Flowdock

Good notes posted by alloy

RSS feed
June 18, 2009
12 thanks

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.