Class Instance Variables in Ruby

AuthorMáximo Mussini
·4 min read

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 😃