November 18, 2006
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
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
Nov 18, 2006
For me
puts Polygon.class_variables
produces nil
ruby 1.8.4, WinXP
Nov 18, 2006
Sorry, I’m wrong. You say ‘reopen class’.
Thanks for nice article.
Nov 18, 2006
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
Nov 19, 2006
@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.
Dec 16, 2006
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.
Dec 17, 2006
@Peter – yep that is what I meant. Corrected.
Jan 05, 2007
This is a great explanation, but I couldn’t get the ClassLevelInheritableAttributes module to work for me.
Jan 11, 2007
@Nate – Are you passing in a hash to the inheritable_attributes method? Can I see the call you are using?
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
def Polygon.sides() @@sides; end
end
class Octagon
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; endend
class MyStringB < MyStringA
@foo = 'bar' def foo() @
foo; endend
a = MyStringA.new
b = MyStringB.new
puts “a.foo: #{a.foo}” # => a.foo: foo
puts “b.foo: #{b.foo}” # => b.foo: bar
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. :-(
Jan 20, 2007
No worries. I removed the other ones. Sorry you had problems.
Mar 08, 2007
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?
Mar 09, 2007
@Barry – Nope. This has nothing to do with Rails or sessions.
Jul 08, 2007
iam in position now, i understand this variables ,,,,,,,,
thanx
jackson francis malugala
DSM institute of technology
dar es salaam
TANZANIA
Jul 11, 2007
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
Jul 11, 2007
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
Jul 18, 2007
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
Oct 04, 2007
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
Oct 05, 2007
@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.
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
Mar 04, 2008
Good Article!
Sorry, comments are closed for this article to ease the burden of pruning spam.