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”

  1. 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.

  2. 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.

  3. Thanks, Ola. Very interesting.

  4. This could be explained as closures work in Ruby.

  5. 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
    => nil
    

    The 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
    => 1
    

    Hurray!

Sorry, the comment form is closed at this time.