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.
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 ::Foo
s. 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
.
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