Ruby class variable scope

January 23rd, 2005

I’ve come across something interesting in Ruby to watch out for. The short version is: be carefull using @@name variables, because they may not be what you think.

Let’s look a sample scenario. You have a User class, and you extend it with diffrent privilages, for example AdminUser, GuestUser.

Since the privilages are static to a class, you simply want to drop them into a variable on the class and not have to worry about it. So let’s write some sample code:

class User @@privilages = {} def self.set_privilage(key, value) @@privilages[key] = value end def self.get_privilage(key) @@privilages[key] end end class GuestUser < User set_privilage :everything, :read end class AdminUser < User set_privilage :everything, :write end

And some test code

puts "GuestUser: #{GuestUser.get_privilage(:everything)}" puts "AdminUser: #{AdminUser.get_privilage(:everything)}"

I don’t know about you, but I didn’t expect what I saw. Here is the result of the test code:

GuestUser: write AdminUser: write

<!-nextpage->

I wondered what went wrong for a while. It wasn’t the fact that the variable was getting set somewhere high, like on Class or something.

class Test1 def self.get_test @@privilages end end Test1.get_test > NameError: uninitialized class variable @@privilages in Test1 > from (irb):25:in `get_test' > from (irb):28 > from (irb):19

It seems that @name variables are completely local to the class they are defined in, so a child class can never access (and doesn't get its own copy of) its parent's @name variables.

The workaround for this is even more interesting, it appears that there is such a thing as @name variables on the class itself, that work in a fashon much more predictable (ie a function defied in a parent accesses the child’s copy of the variable).

This is the modified code:

class User def self.set_privilage(key, value) @privilages ||= {} @privilages[key] = value end def self.get_privilage(key) @privilages[key] end end class GuestUser < User set_privilage :everything, :read end class AdminUser < User set_privilage :everything, :write end

And this time, the test code runs just fine:

GuestUser: read AdminUser: write

Moral of the story, @@name variables are tricky in the least to deal with and tests should be written to make sure you’re getting what you’re expecting, especialy when using inheritance.

7 Responses to “Ruby class variable scope”

  1. Combustion Labs Blog Says:
    Article on T-P-L.com: Ruby class variable scope Yes I'm linking to my own website, but since I discovered this during my personal ruby-ing, that's where it belongs. Please check it out here its worth the read.
  2. Aaron Wheeler Says:
    Page 31 of Pickaxe says that "A class variable is shared among all objects of a class..." I guess an instance variable might be a better option?
  3. Mathieu Jobin Says:
    @@ is a static class variable, not a class instance. so all User object share the same @@privilage the variable would need to be in every class, but then we break inheritence. when I trink about it, whatever the class you call it from, when you do set_previlage, it User::set_privilage that gets called... so ... setting up @@bla in AdminUser would maybe work.
  4. Piotr Banasik Says:
    There actually appear to be 3 variable levels .. class variable class instance variable and instance variable class variables stay in the scope they are defined in, so if you declare it on the parent, they stay on the parent, even if you call a function that changes them on the child. class instance variables are a bit better if you want them to change, they are not inherited from parent, and they are kept local to each unique class and instance variables are local to each instance of the class itself
  5. Mathieu Jobin Says:
    @@ is a static class variable, not a class instance. so all User object share the same @@privilage the variable would need to be in every class, but then we break inheritence. when I trink about it, whatever the class you call it from, when you do set_previlage, it User::set_privilage that gets called... so ... setting up @@bla in AdminUser would maybe work.
  6. Mathieu Jobin Says:
    no aaron, @blah does not work either, cause every single instance, even part of the same class could have different privilage, however, since this is set on the class, it would work.
  7. RubyPrick Says:
    class A class << self attr_accessor :foo end end class B < A; end B.foo = 1 A.foo = 2 p B.foo, A.foo trivial

Leave a Reply