I’ve had a recurring Ruby problem that I’ve had to re-solve every time I come across it - so I’ll document it here once and for all. Usually, I’ll be writing a module to be mixed in to a class - aka a mixin - that needs to provide some sort of function to the class that needs a persistent store unique to that particular class. Just using a class variable isn’t enough, because that class variable will be global to all classes that mixin the module (since the class variable will be a variable on the module object, not on the class objects).
It turns out that the solution is to metaprogrammatically get and set the class variable whenever necessary, using Module#class_variable_get
and Module#class_variable_set
instead of simply calling the variable. When this happens, it’s done on klass
, not on the module.
class Foo
def self.inherited klass
klass.send :class_variable_set, :@@attribute_initializations, []
klass.class_eval do
def self.bar
self.send :class_variable_get, :@@attribute_initializations
end
end
end
end
This method, though ugly, is fairly robust. Try playing with the following - we now have a seperate array for each class that subclasses Foo.
class A < Foo
p @@attribute_initializations
@@attribute_initializations << :a
p @@attribute_initializations
end
class B < Foo
p @@attribute_initializations
@@attribute_initializations << :b
p @@attribute_initializations
end
p A.bar
p B.bar
This works for our scenario as well - a module acting as a mixin:
module Bar
def self.included klass
klass.send :class_variable_set, :@@attribute_initializations, []
klass.class_eval do
def self.bar
self.send :class_variable_get, :@@attribute_initializations
end
end
end
end
class A
include Bar
p @@attribute_initializations
@@attribute_initializations << :a
p @@attribute_initializations
end
class B
include Bar
p @@attribute_initializations
@@attribute_initializations << :b
p @@attribute_initializations
end
p A.bar
p B.bar