Like most object-oriented languages, Ruby has both instance and class variables. The syntax is @name
for instance variables, and @@name
for class variables.
Let's look at a simple example to understand how we might use class variables:
class Animal
@@animals = []
def self.all
@@animals
end
def other_species
@@animals - [self.class]
end
end
class Dog < Animal
@@animals << self
end
class Cat < Animal
@@animals << self
end
Animal.all
# [Dog, Cat]
Cat.new.other_species
# [Dog]
Dog.new.other_species
# [Cat]
The @@animals
class variable is shared among subclasses, and we can refer to it by using the same syntax from both class and instance methods.
Limitations of Class Variables
Now, what happens if we wanted to do something different, like storing metadata or configuration in each subclass?
class Animal
@@sound = '?'
def talk
@@sound
end
end
class Dog < Animal
@@sound = 'woof!'
end
class Cat < Animal
@@sound = 'meow!'
end
Dog.new.talk
# "meow!"
Cat.new.talk
# "meow!"
Because class variables are shared between the parent class and its subclasses, the value of @@sound
gets stepped over by the last subclass, rather than it taking a different value for each subclass as intended.
Class Instance Variables
Fortunately, there's a simple way to achieve this in Ruby:
class Animal
def self.sound
@sound
end
def talk
self.class.sound
end
end
class Dog < Animal
@sound = 'woof!'
end
class Cat < Animal
@sound = 'meow!'
end
Dog.new.talk
# 'woof!'
Cat.new.talk
# 'meow!'
By using instance variables, each subclass gets its own variable so @sound
does not get stepped over, and each subclass can configure the variable as needed. So, how does it work?
Classes in Ruby are plain objects, instances of the
Class
class.
Let that sink in for a bit 😄
Because each class is an object, it can have instance variables just like any other Ruby object.
Although they are often called class instance variables to differentiate them from actual class variables, there's nothing special about them—they are just plain ole' instance variables.
The key practical difference is that class variables (@@
) are shared among a class and all of its descendants, whereas class instance variables (@
) are not shared and each class has separate instance variables just like you would expect from different objects.
Limitations of Class Instance Variables
It's worth noting that in the last example we lost the convenience of referencing the @sound
variable directly on the talk
method like we did in the second example, and instead need to define a getter method at the class level in order to access it.
This is because the same syntax is used for regular instance variables, so we can only refer to class instance variables directly when we are in the class scope—like in class methods and in the top-level context of a class definition.
Also, since we can't use class instance variables to share values between a class and its descendants, in cases where we would like to access the variable of a parent class we will need to refer to it using a fully qualified getter (such as Animal.sound
) or use metaprogramming in order to achieve that.
Driving it home 🏠 🚗
In general, class instance variables are the way to go because they are not shared, which is very useful when building libraries or DSLs, and we don't run the risk of the value getting stepped over by accident in a subclass.
On the other hand, when the variable must be shared by a class and its descendants we should always use class variables.
When inheritance is not in play we can use either, but it's better to be consistent and pick a "default". I always use class instance variables unless I actually need the variable to be shared.
We should now be less fuzzy on what @
means when used in class methods or in a class definition, and when to use class instance variables instead of class variables 😃