diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 787fa26e44..431607f4e0 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0 (pending)* +* Introduce class_attribute to declare inheritable class attributes. Writing an attribute on a subclass behaves just like overriding the superclass reader method. Unifies and replaces most usage of cattr_accessor, class_inheritable_attribute, superclass_delegating_attribute, and extlib_inheritable_attribute. [Jeremy Kemper, Yehuda Katz] + * Time#- with a DateTime argument behaves the same as with a Time argument, i.e. returns the difference between self and arg as a Float #3476 [Geoff Buesing] * YAML serialization for OrderedHash. #3608 [Gregor Schmidt] diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb new file mode 100644 index 0000000000..d74219cb93 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -0,0 +1,36 @@ +require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/module/delegation' + +class Class + # Declare a class-level attribute whose value is inheritable and + # overwritable by subclasses: + # + # class Base + # class_attribute :setting + # end + # + # class Subclass < Base + # end + # + # Base.setting = true + # Subclass.setting # => true + # Subclass.setting = false + # Subclass.setting # => false + # Base.setting # => true + # + # This matches normal Ruby method inheritance: think of writing an attribute + # on a subclass as overriding the reader method. + # + # For convenience, a query method is defined as well: + # + # Subclass.setting? # => false + def class_attribute(*attrs) + attrs.each do |attr| + metaclass.send(:define_method, attr) { } + metaclass.send(:define_method, "#{attr}?") { !!send(attr) } + metaclass.send(:define_method, "#{attr}=") do |value| + metaclass.send(:define_method, attr) { value } + end + end + end +end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb new file mode 100644 index 0000000000..ef84b9f255 --- /dev/null +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -0,0 +1,47 @@ +require 'abstract_unit' +require 'active_support/core_ext/class/attribute' + +class ClassAttributeTest < ActiveSupport::TestCase + class Base + class_attribute :setting + end + + class Subclass < Base + end + + def setup + @klass = Class.new { class_attribute :setting } + @sub = Class.new(@klass) + end + + test 'defaults to nil' do + assert_nil @klass.setting + assert_nil @sub.setting + end + + test 'inheritable' do + @klass.setting = 1 + assert_equal 1, @sub.setting + end + + test 'overridable' do + @sub.setting = 1 + assert_nil @klass.setting + + @klass.setting = 2 + assert_equal 1, @sub.setting + + assert_equal 1, Class.new(@sub).setting + end + + test 'query method' do + assert_equal false, @klass.setting? + @klass.setting = 1 + assert_equal true, @klass.setting? + end + + test 'no instance delegates' do + assert_raise(NoMethodError) { @klass.new.setting } + assert_raise(NoMethodError) { @klass.new.setting? } + end +end