One of the best things about Ruby is that it just does what you mean. The downside of this is that if you’re dealing with a fiddly situation, it can be somewhat hard to work out exactly what it will do.
This is particularly true of constant lookup. To access a constant in ruby, you just use
its name: FooClass
. But how does that actually work?
Module.nesting
To a first approximation, Ruby looks for constants attached to modules and classes in the surrounding lexical scope of your code, for example:
It first looks for A::C::D::B
(doesn’t exist), then A::C::B
(still doesn’t exist), and
then finally A::B
(which does).
Like almost everything in Ruby, the chain of namespaces that will be searched (known to
l33t hackers as the cref
) is introspectable at runtime with Module.nesting
:
If you’ve ever tried to take a short-cut when re-opening a module, you may have noticed
that constants from skipped namespaces aren’t available. This is because the outer
namespaces are not added to Module.nesting
.
In order to find B
, Module.nesting
would have to include A
, but it doesn’t. It only
includes A::C
.
Ancestors
If the constant cannot be found by looking at any of the modules in Module.nesting
, Ruby
takes the currently open module or class, and looks at its ancestors.
The currently open class or module is the innermost class
or module
statement in the
code. A common misconception is that constant lookup uses self.class
, which is not true.
(btw, it’s not using the default definee either, just in
case you wondered):
Object::
Module.nesting == []
at the top level, and so constant lookup starts at the currently
open class and its ancestors. While there’s no class
or module
statement that you can
see, it is taken for granted that at the top level of a ruby file the currently open class
is Object:
Although I’ve not explicitly said it yet, you’ve probably noticed that newly defined
constants also get defined on the currently open class. So constants you define at the top
level end up attached to Object
.
This in turn explains why top-level constants are available throughout your program. Almost all classes in Ruby inherit from Object, so Object is almost always included in the list of ancestors of the currently open class, and thus its constants are almost always available.
That said, if you’ve ever used a BasicObject
, and noticed that top-level constants are
missing, you now know why. Because BasicObject
does not subclass Object
, all of the
constants are not in the lookup chain:
For cases like this, and anywhere else you want to be explicit, Ruby allows you to use
::Kernel
to access Object::Kernel
.
Ruby assumes that you will mix modules into something that inherits from Object
. So if
the currently open module is a module, it will also add Object.ancestors
to the lookup
chain so that top-level constants work as expected:
class_eval
As mentioned above, constant lookup uses the currently open class, as determined by
class
and module
statements. Importantly, if you pass a block into class_eval
or
module_eval
(or instance_eval
or define_method
), this won’t change constant lookup.
It continues to use the constant lookup at the point the block was defined:
Confusingly however, if you pass a String to these methods, then the String is evaluated
with Module.nesting
containing just the class itself (for class_eval
) or just the
singleton class of the object (for instance_eval
).
Other gotchas
Finally I want to point out that if you’re in a singleton class of a class, you don’t get access to constants defined in the class itself:
This is because the ancestors of the singleton class of a class do not include the class
itself, they start at the Class
class.
In a similar vein, it may also worth noting that superclasses of things in
Module.nesting
are ignored. For example:
Summary
Constant lookup in ruby isn’t actually that hard after-all. It just looks at the lexical
nesting of class
and module
statements. You can calculate the currently open class by
using the first value in Module.nesting
, or defaulting to Object
if that array is
empty.
Here’s some code that accurately replicates Ruby’s inbuilt constant lookup. You’ll notice
that I have to use binding.eval with a String so that Module.nesting
is taken from the
binding object and not the block :).