Thursday, June 4, 2009

More Information About Class Variables Than I Ever Wanted to Know

‹prev | My Chain | next›

I am quite unable to resist.

Yesterday, I found a workaround for a problem, but more via guesswork than actual understanding. I hate it when that happens.

The problem occurred when I tried to access a class variable from helper methods mixed into the Sinatra app. The actual error was:
uninitialized class variable @@db in Eee::Helpers
The class variable @@db was defined and Eee::Helpers was the module namespace being mixed into the Sintra application.

Rather than try to work through the Sinatra code (that's some hearty meta-programming!), I try to break the problem down to the simplest possible form.

The simplest possible class that allows class variable access is something along the lines of:
class Foo
@@foo = :foo

def self.foo
@@foo
end
end
Trying this out in an interactive ruby session, Foo.foo returns the expected value, the symbol :foo:
>> Foo.foo
=> :foo
Instead of defining the foo class method as above, it is also possible to do it thusly:
class Foo
@@foo = :foo

class << self
def foo
@@foo
end
end
end
Executing this in irb, I get the exact same results:
>> Foo.foo
=> :foo
One might be tempted to think of the above two forms as identical ways to define class methods, but that is not quite accurate. The first way (def self.foo) creates what one would normally think of as a class method.

The second way opens the class's eigenclass to define the method. Eigenclasses are more useful with instances than with classes. Defining a method on an instance's eigenclass allows you to define a method on a single instance of a class rather than all instances. When trying to resolve where an object's method is defined, Ruby will first check the object's eigenclass before checking "normal" methods.

Since Ruby classes are, themselves, objects, it is possible to use eigenclasses with them as shown above. All that is just fine, but where was I going wrong yesterday? Both earlier examples work just fine.

The trouble occurs when I define the method outside of the class:
class Foo
@@foo = :foo
end
class << Foo
def foo
@@foo
end
end
When I try to access Foo.foo in irb, I get:
>> Foo.foo
(irb):6: warning: class variable access from toplevel singleton method
NameError: uninitialized class variable @@foo in Object
from (irb):6:in `foo'
from (irb):9
A singleton method is another name for an eigenclass class method. The access to the class variable is considered top level because the class keyword does not define a class name that would provide a scope to hold the class variable.

That is still not quite the error that I got yesterday—I did not get the warning about the toplevel singleton object, just the warning about the uninitialized class variable. The reason this was not part of my failure chain yesterday was because I had mixed in the class method:
class Foo
@@foo = :foo
end
module Bar
def foo
@@foo
end
end
class << Foo
include Bar
end
Now, when I try to access Foo.foo, I get the same error that was getting yesterday:
>> Foo.foo
NameError: uninitialized class variable @@foo in Bar
from (irb):6:in `foo'
from (irb):12
And just like yesterday, I can resolve this by by-passing the eigenclass with a direct class_variable_get call:
class Foo
@@foo = :foo
end
module Bar
def foo
self.send(:class_variable_get, :@@foo)
end
end
class << Foo
include Bar
end
Now, when I call Foo.foo, I get the desired symbol :foo from the class variable—even though I get it through the toplevel singleton / eigenclass:
>> Foo.foo
=> :foo
So, it turns out my educated guess from yesterday was actually right. Nine times out of ten, my educated guesses are wildly wrong. It is nice to know that I got this one right—it gives me confidence moving forward that I am not programming by coincidence.

1 comment:

  1. Great post. Just catching up on some of your prolific posts! :)

    ReplyDelete