Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Ruby class_eval remove constants from scope?

Tags:

ruby

I made a class with a constant & a method:

class A
  FOO = 'hello'
  def bar
    puts FOO
  end
end

A.new.bar
=> 'hello'

And everything works as expected. However when I do this:

A.class_eval do
  def bar
    puts FOO
  end
end

A.new.bar
NameError: uninitialized constant FOO

Weirdness... To get around this I'm doing:

A.class_eval do
  def bar
    puts self.class::FOO
  end
end

Any good explanation on why this is so?

like image 321
newUserNameHere Avatar asked Mar 12 '23 08:03

newUserNameHere


1 Answers

Constants are looked up

  1. outwards in the lexically enclosing module declarations
  2. upwards in the inheritance chain of the current module declaration

So, let's just do what Ruby does:

  1. look outwards in the lexically enclosing module declarations: easy – there are no module declarations
  2. look upwards from the current module declaration: again, there is no current module declaration … or is there? Well, if there is no module declaration, it is implicitly assumed to be class Object – but Object doesn't have a constant named FOO and neither do its ancestors Kernel and BasicObject.

Ergo: Ruby is right. There is no constant named FOO within the constant lookup path. It didn't remove FOO from the scope, it was never in scope to begin with.

[Ruby's reflection API actually gives you access to anything you need: #1 is exactly the same as Module.nesting and #2 is (almost) the same as Module.nesting.first.ancestors.]

You might think: wait, isn't module_eval a module declaration? No, it isn't! It's just a method like any other method taking a block that's just like any other block. It doesn't alter the constant lookup rules in any way.

Note that that's not quite true: after all, e.g. instance_eval does change method lookup rules, for example, so it would not be inconceivable that module_eval changes constant lookup rules. And even more confusingly, when called with a String instead of a block, it actually does change Module.nesting and thus the constant lookup rules!

like image 191
Jörg W Mittag Avatar answered Mar 25 '23 01:03

Jörg W Mittag