Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is constant accessibility determined?

Tags:

ruby

constants

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.

like image 796
sawa Avatar asked Jan 26 '16 02:01

sawa


2 Answers

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.

like image 38
fylooi Avatar answered Nov 16 '22 03:11

fylooi


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

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
like image 172
Wand Maker Avatar answered Nov 16 '22 02:11

Wand Maker