Code tips #7: Ruby – Keeping state across class instances and the problem with class variables

Class variables is a typical way to share state across instances of a class. Let’s say we have a Playlist class and we want to count how many instances of this class we have. We can write the following:

class Playlist
  @@number_of_playlists = 0

  def initialize
    @@number_of_playlists += 1
  end

  def play
    p "Playing..."
  end


  def get_number_of_playlists
    @@number_of_playlists
  end
end

The problem comes when

we want to create subclasses of this class, e.g.

class HiphopPlaylist < Playlist
  @@number_of_playlists = 0
end

class RockPlaylist < Playlist
  @@number_of_playlists = 0
end

And now let’s create some instances of these classes:

HiphopPlaylist.new
RockPlaylist.new


p RockPlaylist.new().get_number_of_playlists
# It outputs 3, instead of 2

The problem is that the class variable that is created in the Playlist class is shared across the whole hierarchy. Since in Ruby the Class is also an Object, we can create instance variables that belong to the class object, and then create instance variable accessor.

class Playlist

  def initialize
    self.class.number_of_playlists +=1
  end

  def self.number_of_playlists
    @number_of_playlists ||= 0
  end

  def self.number_of_playlists=(n)
    @number_of_playlists = n
  end


  def get_number_of_playlists
    self.class.number_of_playlists
  end
end

class HiphopPlaylist < Playlist

end

class RockPlaylist < Playlist

end

With that way our subclasses can calculate correctly the number of their playlists, since its of them has instance variable “number_of_playlists” on their object.

p HiphopPlaylist.new().get_number_of_playlists #1
RockPlaylist.new
RockPlaylist.new


p RockPlaylist.new().get_number_of_playlists #3

Leave a Reply

Your email address will not be published. Required fields are marked *