Unexpected behavior when referencing nested classes

It was my understanding that consts are first searched for in their
own module scope before Ruby starts looking elsewhere. So, if you have
a class called X in a module M1::M2 and another one at top level, and
you’re inside M1::M2, then referencing X gets you M1::M2::X.

This doesn’t appear to be the case though:

ruby-1.9.2-p0 :001 > module Foo; end
=> nil
ruby-1.9.2-p0 :002 > module Foo::Bar; class Baz; end; end
=> nil
ruby-1.9.2-p0 :003 > class Baz; def say; “::Baz”; end; end
=> nil
ruby-1.9.2-p0 :004 > class Foo::Bar::Baz; def say; “::Foo::Bar::Baz”;
end; def x; Baz.new.say; end; end
=> nil

expected “Foo::Bar::Baz”

ruby-1.9.2-p0 :005 > Foo::Bar::Baz.new.x
=> “::Baz”

Have I misunderstood how constants work?

~ jf

John F.
Principal Consultant, BitsBuilder
LI: http://www.linkedin.com/in/johnxf
SO: User John Feminella - Stack Overflow

On Sat, May 28, 2011 at 7:21 AM, John F.
[email protected]wrote:

=> nil
Have I misunderstood how constants work?

~ jf

John F.
Principal Consultant, BitsBuilder
LI: http://www.linkedin.com/in/johnxf
SO: User John Feminella - Stack Overflow

I have some vague hypotheses about why it does this, but don’t have any
idea
how to test them.

module Foo
end

module Foo::Bar
class Baz
end
end

class Baz
def say
“::Baz”
end
end

gives the unexpected result

class Foo::Bar::Baz
def say
“::Foo::Bar::Baz”
end
def x # !> previous definition of x was here
Baz.new.say
end
end

Foo::Bar::Baz.new.x # => “::Baz”

gives the expected result

module Foo::Bar
class Baz
def x # !> method redefined; discarding old x
Baz.new.say
end
end
end

Foo::Bar::Baz.new.x # => “::Foo::Bar::Baz”

Have I misunderstood how constants work?

Yeah. Ruby first tries to find the constant in the “lexical scope” of
the reference - immediately enclosing module/class, then the next
enclosing module/class, and so on.

You can find out the modules/classes that are searched by calling
Module.nesting method at that point.

class Foo::Bar::Baz; def say; “::Foo::Bar::Baz”;
end; def x; Baz.new.say; end; end
Foo::Bar::Baz.new.x

Here, Baz is first looked up in Foo::Bar::Baz, and then the global
namespace; confirm with Module.nesting. Ruby finds Baz in the later,
so '::Baz is the expected output.

module Foo::Bar
class Baz
def say; “::Foo::Bar::Baz”; end
def x; Baz.new.say; end
end
end

Foo::Bar::Baz.new.x #=> “::Foo::Bar::Baz”

Here, Ruby first looks up Baz in Foo::Bar::Baz, followed by Foo::Bar
finds, and then the global namespace. Again, you can confirm this with
Module.nesting.

Sometime back, I had answered a similar question. You might want to
have a look1.