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.
January 23rd, 2005 at 01:27 PM 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.
January 24th, 2005 at 01:52 AM 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?
January 24th, 2005 at 05:49 AM @@ 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.
January 24th, 2005 at 05:49 AM 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
January 24th, 2005 at 05:50 AM @@ 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.
January 24th, 2005 at 05:51 AM 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.
March 16th, 2005 at 08:27 AM
class A class << self attr_accessor :foo end end class B < A; end B.foo = 1 A.foo = 2 p B.foo, A.footrivial