Huliax Huliax - 5 months ago 16
Ruby Question

How does Ruby's Object#const_get actually work?

I have recently discovered that Ruby (2.2.1) has some "interesting" behavior.

module Foo
class Foo
end
class Bar
end
end

Foo.const_get('Foo') #=> Foo::Foo
Foo.const_get('Bar') #=> Foo::Bar
Foo.const_get('Foo::Foo') #=> Foo
Foo.const_get('Foo::Bar') #=> NameError: uninitialized constant Foo::Foo::Bar
Foo.const_get('Foo::Foo::Bar') #=> Foo::Bar
Foo.const_get('Foo::Foo::Foo::Bar') #=> NameError: uninitialized constant Foo::Foo::Bar
Foo.const_get('Foo::Foo::Foo::Foo::Bar') #=> Foo::Bar
Foo.const_get('Foo::Foo::Foo') #=> Foo::Foo
Foo.const_get('Foo::Foo::Foo::Foo') #=> Foo
Foo.const_get('Foo::Foo::Foo::Foo::Foo') #=> Foo::Foo
Foo.const_get('Foo::Foo::Foo::Foo::Foo::Foo') #=> Foo


This is a bit surprising. My understanding has been that
const_get
first looks for a constant in the receiver's collection of constants and then looks at Object's constants. OK, fine. Why then does the fourth
Foo#const_get
above fail and the third one doesn't?

I'm also curious as to why calling
Foo#const_get
alternates between the module and class depending on how many
::Foo
s you add.

Answer

The docs say:

This method will recursively look up constant names if a namespaced class name is provided.

So Foo.const_get('Foo::Bar') is basically the same as Foo.const_get('Foo').const_get('Bar'). Using this interpretation your results make sense.

Your third example:

Foo.const_get('Foo::Foo')

is the same as

Foo.const_get('Foo').const_get('Foo')

The first const_get looks at constants defined within the top level Foo (the module), and finds the nested class. So the whole thing effectively becomes:

Foo::Foo.const_get('Foo')

The second call then looks at the class, first looking the any contained constants (finding none) and then looking in its ancestors. Object is an ancestor, and has the top level Foo module as a constant, so this is found and returned.

This also explains the alternation when adding extra ::Foos. The alternation is between const_get on the top level module finding the nested class, and on the nested class looking up the inheritance chain and finding the top level module.

The fourth example, Foo.const_get('Foo::Bar'), which raises an exception, can also be explained. It is equivalent to

Foo.const_get('Foo').const_get('Bar')

The first part, Foo.const_get('Foo') is the same as the case above, evaluating to Foo::Foo, so the whole thing now effectively becomes:

Foo::Foo.const_get('Bar')

Now the nested Foo class doesn’t contain a Bar constant, and looking up the inheritance chain, neither does Object, so the result is a NameError.

Comments