Class-level instance variables

I’ve been doing a lot of metaprograming of late with Radiant and have gained a deep appreciation for class-level instance variables. If you haven’t done a lot with class variables, you probably won’t see much value class-level instance variables.

Maybe you’ve been playing around with those neat little class methods on your Rails model objects:

class Person < ActiveRecord::Base
  validates_presence_of :name
  validates_numerically_of :age
end

Or perhaps you’ve been experimenting with Dwemthy’s Array:

class Dragon < Creature
  life 1340     # tough scales
  strength 451  # bristling veins
  charisma 1020 # toothy smile
  weapon 939    # fire breath
end

Ruby makes creating this kind of Domain Specific Language a snap, but it’s not without it’s
pitfalls if you’ve never done it before.

At first glance you might think that you could store this kind of meta information in a class variable. Let’s take a simple example. You want to create the Creature class that Dragon inherits from. You might define your first attribute like this:

class Creature
  def self.life(life = nil)
    @@life = life || @@life
  end
end

Class variables are a convenient method of storing information in the class object itself. Since class variables look much like instance variables (in that they are preceded with two @ signs) you might be tempted to think that they are just instance variables for classes, but don’t be fooled! Class variables are shared variables. Every subclass of a superclass has access to the superclass’s class variables between superclasses and subclasses. That is, instead of getting a new version of the class variable every time you inherit from the superclass, you get the same version of the variable (not a copy).

In practice this means that class variables are a poor choice for storing meta data. In the example above if we inherit from Creature more than once we quickly see the problem:

class Dragon < Creature
  life 1340
end
class Gwythaint < Creature
  life 250
end
p Dragon.life  #=> 250 !!!!

Instead of getting 1340 for the dragon’s life, we get 250!! Again this is because class variables are shared between the superclass and every one of its subclasses. It has been suggested that an easy way to work around this problem is to use a hash and store class/value pairs for each of the subclasses:

class Creature
  @@lives = Hash.new
  def self.life(life = nil)
    @@lives[self] = life || @@lives[self]
  end
end

This works, but is somewhat verbose. Instead of using a class variable, you can use a class-level instance variable. What?! You thought instance variables were only for instances of the class? Look again:

class Creature
  def self.life(life = nil)
    @life = life || @life
  end
end

The above works because within the context of the class method the instance variable refers to a class-level instance variable—not a normal instance variable. In fact, any time you reference an instance variable outside a method definition you are working with a class-level instance variable:

class MyObject
  @class_level = "a class-level instance variable"
  
  def initialize
    @normal = "a normal instance variable"
  end
end
p MyObject.instance_variables #=> ["@class_level"]
p MyObject.new.instance_variables #=> ["@normal"]

Don’t be confused. Classes in Ruby are also objects. Just like other objects they have their own instance variables. The trick with class-level instance variables is that they are defined after the class is created. With a normal object this would look like this:

o = MyObject.new
p o.instance_variables #=> ["@normal"]
o.instance_eval do
  @new_var = "test"
end
p o.instance_variables #=> ["@normal", "@new_var"]

Most of the time you probably setup instance variables for an object when you initialize it. However, with classes, since instance variables are created after the class is initialized (which happens as soon as you open the class to define methods), instance variables will always be nil when you create a subclass:

class LargeGwythaint < Gwythaint
end
p LargeGwythaint.life #=> nil !!!!

Ruby gives us a nice way of working around this problem with a the inherited class method:


class Creature
  def self.inherited(subclass)
    subclass.instance_variable_set("@life", @life)
  end
end

Which works as expected:


class LargeDragon < Dragon
end
p LargeDragon.life #=> 1340

Of course, you can package all this logic up in its own module:


module InheritableTraits

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def traits(*attrs)
      @traits ||= []
      @traits += attrs
      attrs.each do |attr|
        class_eval %{
          def self.#{attr}(string = nil)
            @#{attr} = string || @#{attr}
          end
          def self.#{attr}=(string = nil)
            #{attr}(string)
          end
        }
      end
      @traits
    end

    def inherited(subclass)
      (["traits"] + traits).each do |t|
        ivar = "@#{t}"
        subclass.instance_variable_set(
          ivar,
          instance_variable_get(ivar)
        )
      end
    end
  end

end

And make your life much easier:


class Creature
  include InheritableTraits
  traits :life, :strength, :charisma, :weapon
end

class Dragon < Creature
  life 1340     # tough scales
  strength 451  # bristling veins
  charisma 1020 # toothy smile
  weapon 939    # fire breath
end

class LargeDragon < Creature
  life 2000     # even tougher
  strength 821  # bulging veins
  charisma 715  # much more vile
  weapon 1213   # scorching flames
end

class Gwythaint < Creature
  life 250      # a large bird
  strength 340  # eats meat
  charisma 191  # ugly
  weapon 524    # beak & talons
end

class LargeGwythaint < Creature
  life 612      # gigantic!
  strength 429  # massive wings
  charisma 95   # extremely ugly
  weapon 842    # razor sharp talons
end

Now go read Why the Lucky Stiff’s article Seeing Metaclasses Clearly for more metaprogramming madness.

© 2013 John W. Long