Permalink
Browse files

Introduce class_attribute to declare inheritable class attributes. Wr…

…iting 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.
  • Loading branch information...
1 parent 2092351 commit 8ae25a8e41168801590fdb95891cc5990b4db21c @jeremy jeremy committed Feb 1, 2010
View
@@ -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]
@@ -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
@@ -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

5 comments on commit 8ae25a8

Nice! The mess before that change always left me confused

Contributor

TomK32 replied Feb 8, 2010

Made make_resourceful break :(

If I understand it correctly I'm supposed to write
class.some_attr
instead of
class..read_inheritable_attribute(:some_attr)

Owner

jeremy replied Feb 8, 2010

TomK32, class_inheritable_attribute is still there; keep using it.

This just introduces a new tool that'll consolidate internal use of inheritable, overridable class attributes.

Contributor

TomK32 replied Feb 8, 2010

make_resourceful wasn'tusing class_inheritable_attribute but read and write_inheritable_attribute and a good/bad check turned up this and the commit after to cause a problem. but i fixed it in make_resourceful, maybe you want to have a look at?
http://github.com/TomK32/make_resourceful/commit/d55f607085728a758c0a31072dfc74b79bdf8a0f

Owner

jeremy replied Feb 8, 2010

That's a good commit, but those methods did not change.

Perhaps you were relying on them being present without requiring the core extension.

In any case, the new code is better :)

Please sign in to comment.