Ruby Instance Variables as Class Variables

In one of my introductory lessons on object-oriented programming, I was given three classes to use which were identical apart from one or two methods or variables (this made sense since they all represented sub-types of one overall type of data).

Just for fun, I tried to "DRY" things up using class inheritance. I wanted to make every line of shared code - variables, accessors, and all - inheritable from a parent class. (Full disclosure: I doubt the following represents any kind of best, or even good, practice - I simply wanted to see if I could do it).


Most of the shared code ran without a hitch when dumped into a parent class and inherited back down. Even for shared methods which had slight differences per class, reopening the methods and incorporating the common code using super was no problem.

The only problem I actually ran into was the class variable @@all and its associated methods, which each class used to maintain and query an array of its instances. Here's an example of the original "WET" code and its intended functionality:

class Kia

  attr_accessor :name

  @@all = []

  def self.all
    @@all
  end

  def initialize(name)
    @name = name
    self.class.all << self
  end

end

class Hyundai
  # same as class Kia
end
Kia.new("Rio")
Kia.new("Soul")

Hyundai.new("Sonata")
Hyundai.new("Elantra")

Kia.all
#=> [#<Kia:0x000000024f39e8 @name="Rio">,
     #<Kia:0x000000031b8250 @name="Soul">]
Hyundai.all
#=> [#<Hyundai:0x0000000311f848 @name="Sonata">,
     #<Hyundai:0x00000003068f80 @name="Elantra">]

Now, here was my original revised class hierarchy:

class Car

  attr_accessor :name
  @@all = []

  def self.all
    @@all
  end

  def initialize(name)
    @name = name
    self.class.all << self
  end

end

class Kia < Car
end

class Hyundai < Car
end

And its unintuitive functionality:

Kia.new("Rio")
Kia.new("Soul")
Hyundai.new("Sonata")
Hyundai.new("Elantra")

Kia.all
#=> [#<Kia:0x00000002d81a60 @name="Rio">,
     #<Kia:0x00000002cdcc90 @name="Soul">,
     #<Hyundai:0x00000002c5e4a8 @name="Sonata">,
     #<Hyundai:0x00000002bc6158 @name="Elantra">]

Hyundai.all
#=> [#<Kia:0x00000002d81a60 @name="Rio">,
     #<Kia:0x00000002cdcc90 @name="Soul">,
     #<Hyundai:0x00000002c5e4a8 @name="Sonata">,
     #<Hyundai:0x00000002bc6158 @name="Elantra">]

What's going on here? You would think (I did, at least) that each class would get its own @@all variable. Actually, even the parent Car class returns the same:

Car.all
#=> [#<Kia:0x00000002d81a60 @name="Rio">,
     #<Kia:0x00000002cdcc90 @name="Soul">,
     #<Hyundai:0x00000002c5e4a8 @name="Sonata">,
     #<Hyundai:0x00000002bc6158 @name="Elantra">]

Why are all three using the same variable? Because the scope of a class variable actually descends throughout the class hierarchy - that is, class variables are not only visible to their class and its instances, but also to classes and instances further down the hierarchy.

Even if we create a new class which inherits from Kia, it will reference the same @@all variable from its 'grandparent' Car:

class KiaSubClass < Kia
end

KiaSubClass.all
#=> [#<Kia:0x00000002d81a60 @name="Rio">,
     #<Kia:0x00000002cdcc90 @name="Soul">,
     #<Hyundai:0x00000002c5e4a8 @name="Sonata">,
     #<Hyundai:0x00000002bc6158 @name="Elantra">]

What to do? I found the solution I was looking for in The Well Grounded Rubyist: use an instance variable instead.

The code would look like this:

class Car

  attr_accessor :name

  def self.all
    @all ||= []
  end

  def initialize(name)
    @name = name
    self.class.all << self
  end

end

class Kia < Car
end

class Hyundai < Car
end

And the resulting functionality resembles the original WET implementation:

Kia.new("Rio")
Kia.new("Soul")
Hyundai.new("Sonata")
Hyundai.new("Elantra")

Kia.all
#=> [#<Kia:0x00000001a8d260 @name="Rio">,
     #<Kia:0x000000019ee200 @name="Soul">]

Hyundai.all
#=> [#<Hyundai:0x0000000194c4f0 @name="Sonata">,
     #<Hyundai:0x000000018a7ce8 @name="Elantra">]

Why does this work? Because the scope of an instance variable is limited to an individual object. The subclasses Kia and Hyundai are different objects from each other and from their parent class Car, and so all three can have their own separate instance variable by the name of @all without them bumping into one another.

class Car
  ...
  def self.all
    @all ||= []
  end

  def initialize(name)
    ...
    self.class.all << self
  end
end

How does this work? You'll notice that @all is not declared directly within the body of Car, but within the class method self.all.1 When this method is called on Car, Kia, or Hyundai (either explicitly or by the initialize method), an @all variable will be created for that class object if it doesn't already exist.

Despite the term "instance variable", instances of the three classes will not receive an @all variable, as it will only ever be declared within the context of the class objects themselves (you can't call a class method on an instance, after all).


While I was able to satisfy my curiosity via this trick, there are some quirks that could be considered user interface design failures:

  • After creating multiple Kia and Hyundai cars, Car.all still returns an empty array, which is correct behavior but a little counter-intuitive. If I had my way, for example, calling Car.all would return an array of all cars regardless of manufacturer, while Kia.all would return only Kia's, etc.
  • As it stands, each class only possesses an @all variable after it receives .all method. If a separate class tried to query the @all arrays of different car classes without going through the .all methods, errors could be raised or nil values could be returned instead of empty arrays. It would probably be ideal if Car and its subclasses could begin their existence with @all assigned to an empty array.

  1. The ||= operator is basically shorthand for, "If the variable on the left exists, return it; otherwise, assign it to the value on the right and return it." Google "Ruby Double Pipe Equals" to find more information. 

Located in Dallas, TX and looking for full-time employment as a web developer.