Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Ruby's Object#const_get actually work?

Tags:

ruby

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 ::Foos you add.

like image 788
Huliax Avatar asked Jun 28 '16 21:06

Huliax


1 Answers

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.

like image 123
matt Avatar answered Oct 05 '22 02:10

matt