November 18, 2006

Posted by John

Tagged beginner, class, and variables

Older: Building A Multi-Site Application

Newer: Fixture Groups

Class and Instance Variables In Ruby

Update (6/13/2008): I posted a bit more on the topic with example uses in the wild.

First, let me preface this article with a fact: I’m new to Ruby, however, new is relative in that statement. I’ve been toying with Ruby for over a year but only in the last two months have I really started to code in Ruby and Rails.

Ruby is the first language I have learned that has involved more than just picking up a new syntax. It has a few concepts that have taken/are taking a bit to grasp (such as iterators and inject). One concept that I think has finally clicked is class variables versus class instance variables.

In the past whenever something was hard to grasp in a few minutes, I would just memorize how to use it and the situations that it was typically used in rather than the “what it does” and “why to do it.” The past weeks I have taken a stand against myself on this issue. I am now forcing myself to open up my fav text editor (TextMate) and hack away until I grasp what is going on.

Class Variables

Class variables are easy. Simply create your new class, use the @@ to denote the variable as class level and add a getter method like so:

class Polygon
  @@sides = 10
  def self.sides
    @@sides
  end
end

puts Polygon.sides # => 10

The issue with class variables is inheritance. Let’s say I want to subclass Polygon with Triangle like so:

class Triangle < Polygon
  @@sides = 3
end

puts Triangle.sides # => 3
puts Polygon.sides # => 3

Wha? But Polygon’s sides was set to 10? When you set a class variable, you set it for the superclass and all of the subclasses.

Final proof:

class Rectangle < Polygon
  @@sides = 4
end

puts Polygon.sides # => 4

Class Level Instance Variables

Ok, so what is a Rubyist to do? Let’s think for a second. What is a class? It’s an object. What can objects have? Objects can have class and instance variables. This means that a class can have instance variables. Let’s reopen our Polygon class and add an instance variable:

class Polygon
  @sides = 10
end

Now, you can use some reflection to check out Polygon’s class and instance variables:

puts Polygon.class_variables # => @@sides
puts Polygon.instance_variables # => @sides

Interesting. So let’s try this inheritance thing again, starting from scratch and this time we’ll use class level instance variables.

class Polygon
  attr_accessor :sides
  @sides = 10
end

puts Polygon.sides # => NoMethodError: undefined method ‘sides’ for Polygon:Class

So why the error? We created the instance method getter and setter using attr_accessor and set the sides instance variable to 10. This is where it is tricky. The attr_accessor created the getter and setter methods for an instance of Polygon, not for the Polygon class itself. Try this:

puts Polygon.new.sides # => nil

We get nil, not method undefined. Now it makes sense, eh? We created methods for the instance method sides. So how do you create a class level instance method?

class Polygon
  class << self; attr_accessor :sides end
  @sides = 8
end

puts Polygon.sides # => 8

This adds the side attribute accessor methods to the class level rather than the instance level. What we are left with is a class level instance variable. So now let’s try inheritance:

class Triangle < Polygon
  @sides = 3
end

puts Triangle.sides # => 3
puts Polygon.sides # => 8

Botta bing. Now each class can have it’s own number of sides. Now the issue is how to set a default value. One would assume that the following would produce 8, which is the default for Polygon…

class Octogon < Polygon; end
puts Octogon.sides # => nil

…but one would be wrong. So how do we get around this? Well, we create a module to do the dirty work that we can included in any class so that this functionality can be reused:

module ClassLevelInheritableAttributes
  def self.included(base)
    base.extend(ClassMethods)    
  end
  
  module ClassMethods
    def inheritable_attributes(*args)
      @inheritable_attributes ||= [:inheritable_attributes]
      @inheritable_attributes += args
      args.each do |arg|
        class_eval %(
          class << self; attr_accessor :#{arg} end
        )
      end
      @inheritable_attributes
    end
    
    def inherited(subclass)
      @inheritable_attributes.each do |inheritable_attribute|
        instance_var = "@#{inheritable_attribute}"
        subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
      end
    end
  end
end

When this module gets included in a class, it adds two class methods: inheritable_attributes and inherited. The inherited class method works the same as the self.included method in the module above. Whenever a class that has included this module gets subclassed, it sets a class level instance variable for each of the declared class level inheritable instance variables (@inheritable_attributes). If that didn’t make sense, this might:

class Polygon
  include ClassLevelInheritableAttributes
  inheritable_attributes :sides
  @sides = 8
end

puts Polygon.sides # => 8

class Octogon < Polygon; end

puts Polygon.sides # => 8
puts Octogon.sides # => 8

Cha ching. Now we are golden. We can even do something like this:

class Polygon
  include ClassLevelInheritableAttributes
  inheritable_attributes :sides, :coolness
  @sides    = 8
  @coolness = 'Very'
end

class Octogon < Polygon; end

puts Octogon.coolness # => 'Very'

I know others have posted on this before but I’m hoping that the actual writing of this article will help ingrain it permanently in my head. Also, like I said right off the bat, I’m relatively new so if I did anything that is not idiomatic or is dead wrong, feel free to correct me. For anyone interested, I zipped up the files I created while I was working this out.

Update (6/13/2008): I posted a bit more on the topic with example uses in the wild



22 Comments

  1. Mark Meves Mark Meves

    Nov 18, 2006

    “Use class instance variables, not class variables” http://blogs.relevancellc.com/articles/2006/11/16/use-class-instance-variables-not-class-variables

  2. For me
    puts Polygon.class_variables
    produces nil

    ruby 1.8.4, WinXP

  3. Sorry, I’m wrong. You say ‘reopen class’.
    Thanks for nice article.

  4. You say: “The attr_accessor created the getter and setter for a method that is an instance of Polygon, not for the Polygon class itself.”
    I don’t think that’s true, the attr_accessor method creates the methods for instances of Polygon. Not for methods that are instances of Polygon, as Polygon is a class and instances of a class are Objects not methods.
    Of course it works with the new method as new returns an Object, in this case of new a new Object of class Polygon, an instance of Polygon.
    But I’m also more used to other languages so feel free to correct me!

    Bart

  5. @Sergey – No problem.

    @Bart – I guess I didn’t proof this enough. I meant class. Thanks for catching it. I’ll update it in the article.

  6. Hi,
    Nice article! Just a small type, I think you meant here “Wha? But Polygon’s sides was set to 1?” to be “..was set to 10?”

    Thanks,

    Peter.

  7. @Peter – yep that is what I meant. Corrected.

  8. This is a great explanation, but I couldn’t get the ClassLevelInheritableAttributes module to work for me.

    
    NoMethodError: undefined method `+' for {}:Hash
            from ./script/../config/../config/../lib/class_level_inheritable_attributes.rb:9:in `inheritable_attributes'
    
  9. @Nate – Are you passing in a hash to the inheritable_attributes method? Can I see the call you are using?

  10. John Lane John Lane

    Jan 17, 2007

    Thanks for your summary; two comments …

    Comment #1:

    If all that is required is inheritable class variables, a one-line class method returning whatever value you wanted might be a little easier.

    For example:

    class Polygon

    1. … some stuff …
      def Polygon.sides() @@sides; end
      end

    class Octagon

    1. … some other stuff …
      def Octagon.sides() 8; end
      end

    It’s not sexy, but seems easier …

    class Rectangle < Polygon
    def Rectangle.sides() 4; end
    end

    class Square < Rectangle; end

    Inheritance works as expected:

    puts Square.sides # => 4

    Though, for more complex uses, I’ve noted that class method inheritance doesn’t always seem to work properly (i.e., as I would expect ;-) in 1.8 … but this seems to have been fixed in 1.9, which brings up a second point:

    Comment #2:

    The semantics for class variables seem to have changed in 1.9. For example, try the following in 1.8 and then in 1.9:

    class MyStringA < String
    @foo = 'foo' def foo() @foo; end
    end

    class MyStringB < MyStringA
    @foo = 'bar' def foo() @foo; end
    end

    a = MyStringA.new
    b = MyStringB.new

    puts “a.foo: #{a.foo}” # => a.foo: foo
    puts “b.foo: #{b.foo}” # => b.foo: bar

  11. John Lane John Lane

    Jan 18, 2007

    Doh. After repeated (three) Mephisto errors, I never was able to post. Or so I thought: apparently all three submissions (eventually) worked (though they didn’t load at the time) … apologies for the multiple posting. :-(

  12. No worries. I removed the other ones. Sorry you had problems.

  13. I understand that Class variables are shared across all user sessions in rails. If I use class instance variables, will each user have his own copy of this variable?

  14. @Barry – Nope. This has nothing to do with Rails or sessions.

  15. jackson francis malugala jackson francis malugala

    Jul 08, 2007

    iam in position now, i understand this variables ,,,,,,,,
    thanx
    jackson francis malugala
    DSM institute of technology
    dar es salaam
    TANZANIA

  16. I know it’s most likely my lack of knowledge as I’m learning this stuff, but it seems a huge over complication to have to define and use a module just to share a class level instance variable’s value with a sub-class. It just seems that in the following code (from above), Octogon.sides should equal 8. Is there a good reason why it doesn’t or is it just a weekness in Ruby?

    class Polygon
    class << self; attr_accessor :sides end
    @sides = 8
    end
    class Octogon < Polygon; end
    puts Octogon.sides # => 8

    Thanks

  17. Actually, on rereading that, it’s starting to sink in. @sides is purely related to that instance of the class. When the class is inherited, it’s a new instance of the class, therefore that value is out of scope. So, we have classes defining their own instance variables that are not inherited by child classes (and are accessible via ClassName.MethodName).

    Also, at the level of object instances of a class, we can define object variables that are then accessible via ClassName.new.MethodName, such as:

    class Polygon
    attr_accessor :sides
    @sides = 10
    end
    puts Polygon.new.sides # => nil

    1 question here – As variable values are not even inherited by objects of a class, is there any way of doing that cleanly?

    I am ranting – hope some of it makes sense

    A

  18. Great post; it helped me understand 2 (yes) different Rails mechanism that I was studying. In fact, I can now perhaps answer a question asked by several in this thread: “is there a way to do this more simply?”; yes, there is.

    If you want to share this feature (ie, allow a class attr to be inheritable but where each class gets its own copy) acrosss all of your Ruby project, just define the code for the attribute directly into ‘class Class’, and all classes will inherit it (without need of the helper module described by John).

    This is in fact how Rails does it; it gives the attribute a beautiful name, ‘class_inheritable_accessor’, and
    codes the feature in a file with the same name (see under active_support/core_ext/class). The result is, for example, that the ActionController::Base can just write one line, eg:

    class_inheritable_accessor :template_root

    Of course, not always this (ie, to reopen and extend the class Class) is wise; then, the mechanism indicated by John (a module with the included/inherited callbacks) is necessary; mechanism that in fact is used by Rails (again!) itself, when it wants to propagate a feature across only a particular inheritance branch.

    So, the readers who were asking ‘something cleaner’ have to reconsider: this is how it must be done (when extending the class Class/Module is not possible)… it is clean! :-)

    raul

  19. Hi John,
    great post!

    I want to share with you a tip.
    I recently came across the same error reported by Nate a few comments before mine
    http://railstips.org/2006/11/18/class-and-instance-variables-in-ruby#comment-1250

    The problem occurs when you use your class within a Rails project.
    Rails uses @inheritable_attributes variable to manage a class inheritable architecture thus if you try to use your class for a Ruby library included into a Rails project it will simply fails!

    You should give to the method and internal variable a name less common.
    Actually, I fixed the issue using

    @inheritable_attribute_list

    and

    def inheritable_attribute_list
    end

  20. @Simone – Nice find and thanks for posting that. I wrote this article as a way to learn and help others. I hadn’t attempted to use it in rails because I knew rails already had facilities for inheritable attributes.

  21. Anirban Deb Anirban Deb

    Oct 31, 2007

    I have a simple workaround for this where the inherited class creates the variable by calling a class method, so there is no need to inherit the parent class variables with this extra module. The base_without_table plugin too uses this approach:

    class Polygon

    class <<self
    def props()
    @props ||= Hash.new
    end
    def prop(name)
    @props=props()
    @props[name]=“length x width”
    @props.each do |key,value| puts “key is #{key} and value is #{value}” end
    end
    end

    end
    class Rect <Polygon
    prop :area
    end

  22. Good Article!

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a programmer who has fallen deeply in love with Ruby. Learn More.

Projects

Flipper
Release your software more often with fewer problems.
Flip your features.