Class Variables – A Ruby Gotcha

Dave Kennedy
Share

Class variables in Ruby have a bad name. It’s true, they are considered harmful, borderline evil. So why the bad rap? Well, most groans are about how they behave when inheritance enters the fray. Correction: It’s all about how they behave compared to what we expect.

To paint a picture of our little problem child lets look at what we expect with PHP.

<?php

class DemoClass {
    public $myvar;

  public function __construct() {
    $this->myvar = "hello world : ";
  }

  public function getMyVar() {
    echo $this->myvar;
  }

}
class Demo2Class extends DemoClass {
    public function __construct() {
        $this->myvar = "goodbye world : ";
    }
}

$demo1 = new DemoClass();
$demo2 = new Demo2Class();

$demo1->getMyVar();
$demo2->getMyVar();
$demo1->getMyVar();

// Produces
// hello world : goodbye world : hello world : "

That makes sense to me. We define a class with a concrete constructor and abstract getter. When inherited we can override the constructor and set the “class variable” to something else. The base class is unaffected, the world keeps spinning.

In Ruby if we try and do something similar using class variables we end up with one or two problems.

class DemoClass
  @@my_var = nil
 
  def initialize
    @@my_var = "hello world"
  end

  def my_var
    puts @@my_var
  end
end

class Demo2Class < DemoClass
  def initialize
    @@my_var = "goodby world"
  end
end

demo1 = DemoClass.new
demo1.my_var
demo2 = Demo2Class.new

demo2.my_var
demo1.my_var

# Produces

#hello world

#goodbye world

#goodbye world

When you approach class variables in Ruby in this manner they just seem wrong. The change to my_var in Demo2Class has bubbled up to our base class. To mitigate the problem, say we subclass DemoClass again the behaviour of the class variable my_var is nothing short of unpredictable. They are global variables in disguise.

So Why Use Class Variables?

On the face of it the rumours about class variables are true. But now we know what not to do, is there a scenario where class level variables are actually useful? Well, I confess. I use them in almost every web application.

Using the methods described in our examples, depending on initialization data implemented using class level variables as a class ‘state’ is a pretty bad idea. However, I do use class variables for initialization data.

Rails applications generally have at least 4 environments, production, staging, development and test. It’s very likely that configuration changes per environment, be it email addresses to mail exceptions to or a URL of a web service sandbox. The code itself looks like:

class AppConfig
  @@app_config = YAML::load(ERB.new((IO.read("#{RAILS_ROOT}/config/app_config.yml"))).result)[RAILS_ENV]

  def self.method_missing(sym, *args, &block)
    @@app_config[sym.to_s]
  end
end

It should be pretty self explanatory. A YAML file is parsed into the class variableapp_config. Then usingmethod_missingat class level I can retrieve values easily withAppConfig.exception_email_addresses`.

Class Level Configuration

So now hopefully we can agree that class variables are not completely evil. Sometimes we do want to share traits at a class level down class hierarchy. A common technique to achieve this without incurring the problems described previously is to use class instance variables.

Although a bit of a mouthful, class instance variables are pretty straightforward, (coming from PHP they did not make sense at first) we just have to adjust our thinking slightly. I always find the best way to discover first is irb.

ruby-1.9.2-p290 :001 > class Demo
ruby-1.9.2-p290 :002?>   @my_instance_variable = "whaaa?"
ruby-1.9.2-p290 :003?>   end
 => "whaaa?"
ruby-1.9.2-p290 :004 > Demo.class
 => Class
ruby-1.9.2-p290 :005 > Demo.superclass
 => Object
ruby-1.9.2-p290 :006 > Demo.instance_variables
 => [:my_instance_variable]

There it is, unlike more classical languages Ruby classes (like everything else) are just objects with instance variables. So how does this knowledge help us? Returning to the previous example and employing a little accessors trick.

class DemoClass
  class << self
    attr_accessor :my_var
  end

  @my_var = nil

  def initialize
    self.class.my_var = "hello world"
  end

  def my_var
    puts self.class.my_var
  end
end

class Demo2Class < DemoClass
  def initialize
    self.class.my_var = "goodby world"
  end
end

demo1 = DemoClass.new
demo1.my_var
demo2 = Demo2Class.new

demo2.my_var
demo1.my_var

# Produces

# hello world

# goodbye world

# hello world

The example is looking pretty horrible now, before any explanation lets clean it up so it offends our eyes no longer.

class DemoClass
  class << self
    attr_accessor :my_var
  end

  @my_var = "hello world"
end

class Demo2Class < DemoClass
  @my_var = "goodby world"
end

puts DemoClass.my_var
puts Demo2Class.my_var
puts DemoClass.my_var

If you are unsure about eigenclasses ( a.k.a singleton classes, but I’m originally a PHP guy and a singleton class meant something totally different at first) in this demonstration we are simply using it to create a attr_accessor as we normally would, only doing it this way creates it for our class instance variable.

Wrapping up

Remember when I said class variables are not always bad and showed an example when they are perfectly acceptable (well in my book anyway). I did come across a problem with that AppConfig snippet.

I had to duplicate an application for two different clients. Obviously the configuration data was different, but the codebase was to be the same. I wanted to maintain the codebase in a single SCM repository (bug fixes or updates would apply to both versions) and I didn’t want to hit git merge mashes, in fact I didn’t want the SCM to take on a new role in my workflow, it’s there to do SCM only, not manage configuration across clients.

Obviously, we would look to persist the configuration in a database, which is simple to achieve in Rails using an ActiveRecord model like so:

# app/models/application_config.rb

class ApplicationConfig < ActiveRecord::Base
  serialize :config_value
  after_save :reset_config

  def reset_config
    AppConfig.reset_configs
  end
end

# lib/app_config.rb

class AppConfig
  class << self
    attr_accessor :configs
  end

  @configs = {}

  def self.method_missing(sym, *args, &block)
    @configs[sym.to_s] ||= ""
  end

  def self.reset_configs
    self.configs = {}
    configs = ApplicationConfig.all

  configs.each do |setting|
    self.configs = {setting.config_key => setting.config_value}.merge self.configs
  end
  end
end

Using the code above I could drop in a persisted configuration for the application without refactoring any code where the configuration data was being accessed (this was a big win as it was a pretty large app). I could also use a simple scaffold to update and edit configuration on the fly. The use of class instance variables allowed me to cache the config data without hitting the database for every method_missing message the class received.

Hopefully, that has dispelled the class variables are useless/evil myth. Like most things, a lack of understanding and naive implementations gives class variales a bad name. It certainly took me a while (and plenty of naive implementations) to get a grasp of what’s going on with them. As for the eigenclass stuff, check out Nathan Kleyns article on metaprogramming for more detail.

CSS Master, 3rd Edition