The Rails 3 Way Highlights: ActiveSupport's Class.class_attribute
As I'm wrapping up work on The Rails 3 Way, I'm coming across little nuggets of interesting content suitable for blogging. Time permitting, I'll publish them here over the course of the next few months.
In this case, I cover a new ActiveSupport core extension to Ruby's Class object. It allows creation of class-level attributes in a simple and powerful manner that I think is a lot clearer than the mess of cattr_ and class_inheritable_ methods that precede it.
class_attribute(*attrs)
The class_attribute method declaratively defines one or more class-level attributes whose value is inheritable and overwritable by subclasses and instances, like so:
class Base
class_attribute :setting
end
class Subclass < Base
end
>> Base.setting = "foo"
=> "foo"
>> Subclass.setting
=> "foo"
>> Subclass.setting = "bar"
=> "bar"
>> Subclass.setting
=> "bar"
>> Base.setting
=> "foo"
This behavior matches normal Ruby method inheritance: think of writing an attribute on a subclass as overriding the parent's reader method. Instances may overwrite the class value in the same way. (Note that the following code samples create anonymous classes to illustrate usage in a more concise fashion.)
klass = Class.new { class_attribute :setting }
object = klass.new
>> klass.setting = "foo
=> "foo"
>> object.setting = "bar"
=> "bar"
>> klass.setting
=> "foo"
To opt out of the instance writer method, pass :instance_writer => false.
klass = Class.new { class_attribute :setting, :instance_writer => false }
>> klass.new.setting
=> NoMethodError
The class_attribute method also works with singleton classes, as can be seen in the following example.
klass = Class.new { class_attribute :setting }
>> klass.singleton_class.setting = "foo"
=> "foo"
For convenience, a query method is defined as well, which allows you to see if an attribute has been set on a particular class instance.
klass = Class.new { class_attribute :setting }
>> klass.setting?
=> false
>> klass.setting = "foo"
=> "foo"
>> klass.setting?
=> true