cattr_accessor
cattr_accessor(*syms, &blk)No documentation available.
# File activesupport/lib/active_support/core_ext/class/attribute_accessors.rb, line 75
def cattr_accessor(*syms, &blk)
cattr_reader(*syms)
cattr_writer(*syms, &blk)
end 3Notes
Important note
It has been said that “it can be compared to, but isn’t the same thing as”:
class Bar
class << self
attr_accessor :greeting
end
end
Which is true. However, they are “inherited” isn't exactly the case. Rather, cattr_accessor uses class variables.
The problem with class variables in Ruby, is that a class variable is the same object across all subclasses of a class. Consider the following example of what happens with cattr_accessor:
class A
@@foo = 'foo'
def self.foo
@@foo
end
end
p A.foo # => "foo"
class B < A
end
p B.foo # => "foo"
class B
@@foo = 'bar'
end
p B.foo # => "bar"
So far so good you might think. However, something you might not have expected is that the variable has now also changed in class A:
p A.foo # => "bar"
This is in my opinion almost never what you'd want. More probable is that you'd want the individual class instance to have an accessor. (Remember classes are objects in Ruby). I do the following in regular Ruby:
class A
class << self
attr_accessor :foo
end
self.foo = 'foo'
end
p A.foo # => "foo"
class B < A
end
p B.foo # => nil
class B
self.foo = 'bar'
end
p B.foo # => "bar"
p A.foo # => "foo"
As you can see, this returns +nil+ when a value hasn't explicitly been set yet on the new class instance. If you'd like to have inheritance without messing with the superclasses variables, have a look at ActiveSupport’s class_inheritable_accessor, which does the same as I just explained, but creates a clone of the object and assigns it to the subclass whenever a class is inherited.
What I’d normally do in Ruby to fix the issue of it returning +nil+ is to create the accessor manually and have it set the instance variable to the default if it’s +nil+:
class A
class << self
def foo
@foo ||= 'foo'
end
end
end
class B < A
end
p B.foo # => nil
So to recap:
- cattr_accessor uses class variables (@@foo), in which case the object is shared across all subclasses of a class. Use it mainly for static data, in which case you'd probably best use a constant.
- class_inheritable_accessor (or what I showed) uses instance variables (@foo) at the Class instance level. These variables are not shared across all subclasses.
Usage
This defines attr_accessors at a class level instead of instance level. class Foo cattr_accessor :greeting end
Foo.greeting = "Hello"
This could be compared to, but is not the same as doing this: class Bar class << self attr_accessor :greeting end end
Bar.greeting = "Hello"
The difference might not be apparent at first, but cattr_accessor will make the accessor inherited to the instances: Foo.new.greeting #=> "Hello" Bar.new.greeting # NoMethodError: undefined method `greeting' for #Bar:0x18e4d78
This inheritance is also not copy-on-write in case you assumed that: Foo.greeting #=> "Hello" foo1, foo2 = Foo.new, Foo.new
foo1.greeting = "Hi!"
Foo.greeting #=> "Hi!"
foo2.greeting #=> "Hi!"
This makes it possible to share common state (queues, semaphores, etc.), configuration (max value, etc.) or temporary values through this.
cattr_accessor_with_default
Class attribute assessors are neat if you want to set up modifiable constant-like varibles. This is how you'd normally set it up:
module MyPlugin
class Conf
@@awesome_level = 'huge'
cattr_accessor :awesome_level
end
end
Then you can call and modify it like this:
>> MyPlugin::Conf.awesome_level
=> 'huge'
>> MyPlugin::Conf.awesome_level = 'massive'
>> MyPlugin::Conf.awesome_level
=> 'massive'
If you have a pile of those accessors I'd do something like this (there might be a better way, but it works):
module MyPlugin
class Conf
def self.cattr_accessor_with_default(name, value = nil)
cattr_accessor name
self.send("#{name}=", value) if value
end
cattr_accessor_with_default :awesome_level, 'huge'
cattr_accessor_with_default :speed_level, 'insane'
cattr_accessor_with_default :indifferent_level
cattr_accessor_with_default :craziness_level, 'nuts'
end
end
This way you declare accessor and it's optional default value on the same line