Gotcha: Constants in anonymous Ruby classes and mixins
I thought I had a reasonable grasp of Ruby and its constants, rarely these days having a wat? moment, but today I stumbled on something I find really quite surprising. Particularly so as I’ve never come across it before and I’ve read my fair share of Ruby books, posts and articles.
Have you ever used constants in anonymous mixins and classes? Well read on …
I use (create) anonymous mixins and classes all the time, they are the rule for me rather than the exception. If I need to create a named class (e.g. MyClass) from the anonymous variable (e.g. my_anon_class), I do an explicit assignment (e.g. MyClass = my_anon_class).
Most Rubyists will be familiar with using constants in classes and mixins, there are an easy way of providing instances methods with access to class-level data. (Class variables do the same but bring some baggage.)
For example, a trivial example:
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass
MYDATA = [1, 2, 3]
def show_data
puts("MYDATA is #{MYDATA}")
end
end
my_inst = MyClass.new
my_inst.show_data
This produces the expected result:
1
MYDATA is [1, 2, 3]
Let’s try this with an anonymous class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my_anon_class = Class.new do
self::MYDATA = [1, 2, 3]
def show_data
puts("MYDATA is #{MYDATA}")
end
end
MyClass = my_anon_class
my_inst = MyClass.new
my_inst.show_data
This fails with the following:
1
2
testcon2.rb:6:in `show_data': uninitialized constant MYDATA (NameError)
from testcon2.rb:16:in `<main>'
As it the norm for anonymous classes and mixins, I’ve explicitly bound MYDATA to self and the constant has really been created - a bit of extra code proves it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my_anon_class = Class.new do
self::MYDATA = [1, 2, 3]
def show_data
puts("MYDATA is #{MYDATA}")
end
end
MyClass = my_anon_class
MyClass.constants.each {|c| v = MyClass.const_get(c, false); puts("MyClass c #{c} v #{v}") }
my_inst = MyClass.new
my_inst.show_data
produces:
1
2
3
MyClass c MYDATA v [1, 2, 3]
testcon3.rb:6:in `show_data': uninitialized constant MYDATA (NameError)
from testcon3.rb:19:in `<main>'
The explicit const_get finds MYDATA (and any other constants) no problem.
To labour the point, a minor change to the syntax of the show_data method to explicitly access the class-level constant also demonstrates that the constant is really available in an instance method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my_anon_class = Class.new do
self::MYDATA = [1, 2, 3]
def show_data
puts("MYDATA is #{self.class::MYDATA}")
end
end
MyClass = my_anon_class
my_inst = MyClass.new
my_inst.show_data
produces the expected output:
1
MYDATA is [1, 2, 3]
I’ve done a complementary set of tests with mixins as well.
The take-away: bare constant names (as in the first example) just don’t work in anonymous classes and mixins. This seems to be a compilation issue and it appears impossible to persuade Ruby (e.g. by explicitly re-setting the constant post class creation) to find the constant using its bare name.
Which is tedious.