I’ve been playing a lot with class variables (aka ‘the double at’) lately. They’re very interesting. How comfortable are you with them? If I were to write some Pickaxe 101 Ruby you’d be okay, right? I mean, some stuff basically off page 33. Okay.
class Auto def how_many_wheels @@wheels end end class Car < Auto @@wheels = 4 end class Motorsickle < Auto @@wheels = 2 end
We’re setup. What happens when I do this:
>> hammer = Motorsickle.new => #<Motorsickle:0x5935a8> >> hammer.how_many_wheels
Will it print 2?
NameError: uninitialized class variable @@wheels in Auto
Of course not. Though this would work with instance variables, our Auto class knows nothing about the class variables Car and Motorsickle keep. Because how_many_wheels is technically being called via Auto and Auto doesn’t have the @@wheels class variable, we get the above exception. Class variables, as the Pickaxe tells us, are “private to a class and its instances.” Remember that.
Let’s give Auto some @@wheels love.
class Auto @@wheels = 0 def how_many_wheels @@wheels end end class Car < Auto @@wheels = 4 end class Motorsickle < Auto @@wheels = 2 end
Now what will we get when we call the same code we called earlier?
>> hammer = Motorsickle.new => #<Motorsickle:0x593530> >> hammer.how_many_wheels => 2
Hey, alright! I mean, no! Wait! Stay in the same context, let’s try Car.
>> blazer = Car.new => #<Car:0x590bb4> >> blazer.how_many_wheels => 2
Crap. Why’s that? It should be 4.
Turns out that when you initialize a class variable in a class and then subclass it, the subclass doesn’t have its own instance of the class variable. Rather, the subclass works directly with the parent’s class variable. So Car and Motorsickle, when playing with what they believe is their own private version of @@wheels, are really acting directly on Auto’s class variable. Remember that, too.
Dr Nic explains this behavior and a workaround in Rails, class_inheritable_accessor, in this post from August.
Oh, but there’s more.
class Computer end class Dell < Computer @@gigarams = 5 def speed @@gigarams end end class Apple < Computer @@gigarams = 90 def speed @@gigarams end end class Computer @@gigarams = 0 def speed @@gigarams end end class Compaq < Computer @@gigarams = 1000 def speed @@gigarams end end
Gigarams, naturally, measure a computer’s speed. Here we see quite clearly and scientifically that Apple computers are all 85 gigarams faster than Dell computers. Compaqs lead the bunch at roughly 1000 gigarams.
In the above code, do we all know what speed is going to print? Let’s run through the list.
>> Dell.new.speed => 5 >> Apple.new.speed => 90 >> Computer.new.speed => 1000 >> Compaq.new.speed => 1000 >> Apple.new.speed => 90
Interesting indeed. This example shows us that class variable references are determined on inherit. Dell and Apple maintain their own @@gigarams class variable while Compaq and Computer share one, as illustrated further up. You could even write code to change Apple’s @@gigarams variable and still be safe. But it’s confusing, yeah? Stay away.
“That computer code isn’t very DRY,” you say. “I’m gonna DRY it up and show this Err bastard how to program.” Go ahead.
class Computer end class Dell < Computer @@gigarams = 5 end class Apple < Computer @@gigarams = 90 end class Computer @@gigarams = 0 def speed @@gigarams end end class Compaq < Computer @@gigarams = 1000 end
“Even though I defined speed after I defined Dell and Apple, I’ll still be able to call it on any instances I create because Ruby has open classes! Perfect!” Yeah, that’s what they all say. But think back: our first example was ruined because Auto didn’t have the @@wheels class variable. Since Computer does the class variable in question, you can be sure running speed on an instance of, say, Dell will give us Computer’s class variable, not Dell’s.
>> Dell.new.speed => 1000 >> Computer.new.speed => 1000
Lesson learned. Double at’s are tricky things. It’s worth noting that class variables will not be inherited in Ruby 1.9, but that’s a long way off.
Finally, here’s a non-Rails solution which may interest you.
class Storm class << self attr_accessor :intensity end end class ThunderStorm < Storm self.intensity = 100 end class WinterStorm < Storm self.intensity = 50 end class GlassStorm < Storm end
Nothing new. We’re setting an instance variable on Storm’s singleton class. All its children, when calling the accessor methods, set and read instance variables on their own singleton classes. How clean and deliciously meta.
>> ThunderStorm.intensity => 100 >> WinterStorm.intensity => 50 >> GlassStorm.intensity => nil >> GlassStorm.intensity = 7 => 7 >> GlassStorm.intensity => 7 >> WinterStorm.intensity => 50 >> Storm.intensity => nil