method

cattr_accessor

v3.1.0 - Show latest stable - Class: Class
cattr_accessor(*syms, &blk)
public

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

alloy · Jun 18, 200912 thanks

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

Mange · Mar 4, 20095 thanks

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

Oleg · Jun 12, 20093 thanks

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