Stumbling around the rubyverse, I happened across an old article by @jnunemaker (of mongo mapper and twitter gem fame) about class variables and inheritance in Ruby, and realized that though I often use class instance variables (i.e., @ variables in the eigenclass), i rarely use class variables (@@), and that I’d never known about the class variable inheritance gotcha.
After following links to various sources on the topic, I felt that much of the discussion further confused the matter (at least for me). There are truly simple solutions to this problem; however, the solution you choose should vary depending upon whether or not you need mutable or non-mutable class attributes.
The problem with @@ inheritance: it’s all the same object
First, lets look at the problem:
# sh$ irb
class Wolf
@@species = 'canis lupus'
def Wolf.species
@@species
end
end
puts Wolf.species # ==> "canis lupus"
Great, now we’ve got a class variable that can be accessed in both class methods and instance methods through @@.
class Dog < Wolf
@@species = 'canis lupus familiaris'
end
puts Dog.species # ==> "canis lupus familiaris"
Perfect. Now our Wolf and Dog both have their own unique species value. Or do they?
puts Wolf.species # ==> "canis lupus familiaris"
Uh oh. When we set @@species on Dog, it changed @@species on Wolf. Why? Because class variables are shared across all ancestors of a class. They all point to the exact same object in memory.
If your goal is to have inheritable class-level attributes that don’t point to the same object in memory, and can thus be differentiated for each class ancestor, you have many options.
Non-mutable Class Attributes
The first, and simplest option, applies if your attribute is really something that shouldn’t be mutable. For example, a Dog’s species is “canis lupus familiaris”, and it will always be “canis lupus familiaris”, and if we were actually designing some kind of OO library that encapsulated the behavior and properties of a Dog, we probably wouldn’t want to allow people using the library to change the value of the dog’s species.
In that case, you don’t even need a class variable. A simple constant will do:
# sh$ irb
class Wolf
SPECIES = "canis lupus"
end
puts Wolf::SPECIES # ==> "canis lupus"
class Dog < Wolf
end
puts Wolf::SPECIES # ==> "canis lupus"
puts Dog::SPECIES # ==> "canis lupus"
class Dog
SPECIES = "canis lupus familiaris"
end
puts Wolf::SPECIES # ==> "canis lupus"
puts Dog::SPECIES # ==> "canis lupus familiaris"
Of course, Ruby being the extremely permissive language that it is, actually allows you to change the values of constants, and merely throws a warning when you do:
Dog::SPECIES = "felis silvestris catus"
# ==> WARNING: already initialized constant SPECIES
So, another option up your sleeve (again, for non-mutable class attributes) is using inheritable class methods:
# sh$ irb
class Wolf
def Wolf.species
"canis lupus"
end
end
puts Wolf.species # ==> "canis lupus"
class Dog < Wolf
end
puts Wolf.species # ==> "canis lupus"
puts Dog.species # ==> "canis lupus"
class Dog
def Dog.species
"canis lupus familiaris"
end
end
puts Wolf.species # ==> "canis lupus"
puts Dog.species # ==> "canis lupus familiaris"
This is option is also straightfoward. However, through the magic of monkey patching, a user of your library could still change the value returned by Dog.species - and this time Ruby won’t even throw a warning! :-)
def Dog.species
"felis silvestris catus"
end
puts Dog.species # ==> "felis silvestris catus"
puts Wolf.species # ==> "canis lupus"
Doh! In the end, there’s probably not much you can do to make something truly immutable in Ruby. Ruby treats you like an grown up - it gives you rules, but offers ways for you to break them, and trusts that you know what you’re doing.
Mutable class attributes
So what if we want mutable class level attributes that are unique in memory through inheritance? Before we result to using any libraries out there for accomplishing this, lets see what we can do with the base language itself:
# sh$ irb
class Wolf
def Wolf.species
@species ||= "canis lupus"
end
def Wolf.species=(value)
@species = value
end
end
puts Wolf.species #==> "canis lupus"
Wolf.species = "wolfy wolf"
puts Wolf.species #==> "wolfy wolf"
Notice that I’ve used “@species” in Wolf.species instead of “@@species”. “@species”, in that context, is a /class/ instance variable - it’s unique to the Wolf eigenclass, and points to a unique location in memory.
Another way to write Wolf would have been:
# sh$ irb
class Wolf
class << self
attr_accessor :species
def species
@species ||= "canis lupus"
end
end
end
puts Wolf.species #==> "canis lupus"
Wolf.species = "wolfy wolf"
puts Wolf.species #==> "wolfy wolf"
Based on a recent poll about class method declaration preferences (which I will post a link to as soon as I can find it), it seems that the vast majority of Rubyists would prefer the way we first defined the “species” and “species=” class methods. However, since a ton of popular Ruby code uses the latter approach, you should definitely familiarize yourself with it.
Now, let’s see what happens when we inherit from Wolf
class Dog < Wolf
end
puts Wolf.species #==> "wolfy wolf"
puts Dog.species #==> "canis lupus"
Success! Now all that’s left to do is give the Dog its proper species value:
# sh$ irb
class Wolf
def Wolf.species
@species ||= "canis lupus"
end
def Wolf.species=(value)
@species = value
end
end
class Dog < Wolf
def Dog.species
@species ||= "canis lupus familiaris"
end
end
puts Wolf.species #==> "canis lupus"
puts Dog.species #==> "canis lupus familiaris"
Wolf.species = "wolfy!"
Dog.species = "puppy!"
puts Wolf.species #==> "wolfy!"
puts Dog.species #==> "puppy!"
If you want an even more concise method for accomplishing this same task, check out class_inheritable provided by rails