Metamagical class variables in Ruby
Recently, while writing a Rails plugin, I ran into an interesting yet brain cracking issue regarding class variables in Ruby. After some research I am still not sure whether this really is an issue or one of Ruby’s features. Let me start off by describing the problem with the plugin; it looked like this:
module ClassMethods
def authenticate_with_header(header, options = {})
#...
@@configuration = {}
cattr_reader :configuration
#...
end
end
When the plugin is initialized, ActionController is extended with this module, so the authenticate_with_header method becomes available as a class method in all my controllers. Just like the way many other plugins work.
As cattr_accessor internally uses a class variable (see for yourself), I define that class variable to give it a value first (obviously in the real code this is not an empty hash), so that MyController.configuration would return this value. But what surprised me was that it returned nil instead… What was going on here? Apparently the attribute getter method was successfully mixed in, but the return value clearly wasn’t that of the class variable!
So I decided to simulate this in an isolated and simplified script:
require 'rubygems'
require 'active_support'
class Pie
@@radius = 40
cattr_reader :radius
def self.reset_radius
@@radius = 20
end
end
Pie.reset_radius
p Pie.radius # => 20
The script outputs “20″ and that puzzled me. The only difference between my plugin and this script is that my plugin dynamically mixes in the class method when it is initialized, where in the script the method (set_radius) is defined when the class is loaded. So let’s try to add this method after the class has been loaded now:
require 'rubygems'
require 'active_support'
class Pie
@@radius = 40
cattr_reader :radius
end
def Pie.reset_radius
@@radius = 20
end
Pie.reset_radius
p Pie.radius # => 40 (!)
Ha, there’s our little bug (or feature)! reset_radius did not set the class variable we expected it to reset. So, what did it set then? After some searching I figured out the class variable was set on Pie’s meta class, which was created the moment we dynamically defined a new class method on it:
class Object
def metaclass
class << self; self; end
end
end
p Pie.metaclass.send :class_variable_get, :@@radius # => 20
So it seems class variables that are set by a dynamically added method are defined on the meta class. As Ruby looks up the hierarchy when requesting a class variable it won’t look in the meta class if it finds it in the class itself. If you look at the implementation of cattr_reader you’ll see that it defines the class variable. And that’s why in our script Ruby doesn’t look in the meta class, even if we remove @@radius = 40 from the class definition. It will just return the first occurrence of @@radius it encounters, which would then have a value of nil.
However, this still doesn’t solve the issue with the plugin. In the plugin, for what I know, there’s no class variable with that same name defined on the class itself. (cattr_reader also applies to the meta class there.) So it should look for the variable I set in the meta class and not return nil.
Asking around didn’t get me much further than the good advice:
“Avoid the usage of class variables as much as you can, they’re weird.”
So I respected that advice, and worked around the issue by defining the getter method myself, circumventing the use of a variable to store the configuration in by just letting it return the configuration hash.
module ClassMethods
def authenticate_with_header(header, options = {})
#...
self.class.send :define_method, :configuration do
return { ... } # config hash
end
#...
end
end
It works, but I can’t say I have been sleeping particularly well lately. So if you have an explanation to this, please let me know by leaving a comment.

After another look and some fooling around with different ways of defining setter methods that set class variables, I found out that those different ways set the class variable in different places. If the method is mixed in, like ‘authenticate_with_header’ in the first example on this page, then the class variable is set on the metaclass (aka singleton class) of the class this module was mixed in to. The problem here is that the getter method created by ‘cattr_reader’ doesn’t look there, it only tries to find the class variable on the class itself and if it can’t find it it will initialize it there with the value of ‘nil’. I’ll put up a new blog post to illustrate this soon!