Sep 122007
I was playing with some code the other day, and I noticed that constants work strangely in class_eval blocks. Sometimes it can find the constant, and sometimes it cannot.
For example, say we have a class with a constant:
class Foo CONS = 'const' end
Now, if we reopen the class and print the constant, it works as expected:
>> class Foo >> puts CONS >> end const
However, if we use a class_eval instead of reopening the class, ruby cannot find the constant:
>> Foo.class_eval { puts CONS } NameError: uninitialized constant CONS from (irb):12 from (irb):11:in `class_eval' from (irb):11
It gets even stranger. We can still see the constant in the list of constants:
>> Foo.class_eval { puts self.constants } CONS
Furthermore, a const_get still works:
>> Foo.class_eval { puts const_get(:CONS) } const
Finally, if we try the class_eval again with a string instead of a block, it works:
>> Foo.class_eval "puts CONS" const
Apparently, constants have a problem with blocks inside of class_eval.
5 Responses to “Ruby constants have weird behavior in class_eval”
Sorry, the comment form is closed at this time.
I think this is a feature. Every block is scoped to the environment where it is created. So the constant is searched in the outer scope. This allows you to do things with class_eval that use variables outside of the class itself as parameters.
To make it more catchy:
CONS = 'outer_const' class Foo CONS = 'inner_const' end Foo.class_eval { puts CONS } #=> "outer_const" 1.times { puts CONS } #=> "outer_const"Would be strange if CONS would be searched within Fixnum.
This is not really a feature as schmidt suggests. Rather, it’s an artifact of the way Ruby’s parsing works. Basically, a “real” constant reference will have it’s cref set during parse time – meaning it will be resolved using real lexical scope. The difference here is that when you list all constants in a scope or use const_get, you’re really using the dynamic scope, which makes a huge difference within class_eval/module_eval/instance_eval.
Hope that cleared things up. It’s not really a feature, and not necessarily a bug; just quirky behavior.
Thanks, Ola. Very interesting.
This could be explained as closures work in Ruby.
This quirky behavior seems to be fixed in Ruby 1.9:
# irb >> RUBY_DESCRIPTION => "ruby 1.9.1p0 (2009-01-20 revision 21700) [i686-linux]" >> class Foo >> CONS = 'const' >> end => "const" >> Foo.class_eval { puts CONS } const => nilThe same goes for the second example you posted:
# irb >> CONS = 'outer_const' => "outer_const" >> class Foo >> CONS = 'inner_const' >> end => "inner_const" >> ?> Foo.class_eval { puts CONS } #=> "outer_const" inner_const => nil >> 1.times { puts CONS } #=> "outer_const" outer_const => 1Hurray!