Ruby seems to have undergone changes several times with respect to constant accessibility. For Ruby 1.9.2, there is description regarding constant accessibility in the question and answer here, but what is written there is not true anymore.
In Ruby 2.3, if I have a constant defined in a class:
class A
Foo = :a
end
I cannot access it via instance_eval
or class_eval
:
A.new.instance_eval{Foo} # => error
A.class_eval{Foo} # => error
A.instance_eval{Foo} # => error
although I can access it from the class body:
class A; Foo end # => :a
How does constant accessibility work as of Ruby 2.3? If possible, please explain the historical changes of constant accessibility in Ruby and the arguments that led to them.
Constant lookup in Ruby uses lexical scope which traverses the current scope and containing modules. The lookup path can be viewed via Module.nesting
> module Out
> module In
> puts Module.nesting
> end
> end
Out::In # gets searched for a given constant first
Out
The lookup remains the same inside a block to enable closure behavior, even for class_eval
.
However, in Ruby 1.9, the receiver for instance_eval
, instance_exec
,
class_eval
, and class_exec
was prepended to the lookup path, which means all constant references would be looked up in the receiver's scope first.
Yehuda Katz raised an issue citing significant breakage:
Consider the case of RSpec:
describe "Date" do it "equals itself" do Date.today.should == Date.today end end
If we use dynamic scope first, then if RSpec adds Spec::Date, it will suddenly break this spec. Lexical scope is more intuitive, and is expected by many normal uses today.
The behaviour was subsequently reverted back to 1.8.
Note: Below answer is based on Ruby 2.2, have not verified on 2.3
As per documentation of Module#class_eval
Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected.
Though below code errored out,
A.class_eval{Foo} # uninitialized constant Foo (NameError)
following works where a string is being eval
ed.
A.class_eval("Foo") #=> :a
It also seems that Constant requires Module name or Class name for it to be evaluated properly. Below works:
A.class_eval{self::Foo} #=> :a
So, does these:
A.new.instance_eval {self.class::Foo} #=> :a
A.instance_eval {self::Foo} #=> :a
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With