Ruby constant resolution has always been somewhat confusing to me. In this article I’m going to demistify it for myself and hopefully help other readers.
What is a constant?
Ruby constant is anything that starts with a capital.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Yes, regular ALL_CAPITAL
are constants, module and class names are constants too.
How Ruby searches constants.
When Ruby tries to resolve a constant, it starts looking in current lexical scope by searching the current module or class. If it can’t find it there, it searches the enclosing scope and so on.
It’s easy to see the lexical scopes search chain with Module::nesting method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Module::nesting
returns an array of searcheable lexical scopes, starting from current.
In above case the search for A_CONSTANT
starts from module C, then goes to enclosing scope – module B, and then to module A where it finally finds it.
Nesting modules using alternative syntax
You’ve probably seen the alternative way of defining the enclosing modules:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
See the difference? Constant resolution only uses the innermost module for searching, ignoring the enclosing scopes. By defining the modules with this shorter syntax you lose the ability to search for constants in enclosing scopes.
Inheritance
Enclosing scopes is the first place where Ruby searches the constants. Second place is the inheritance hierarchy. Consider this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Mixins
Ruby can mixin modules into classes as an alternative to inheritance. When a class mixes in a module, this module inserts itself between the class being mixed in and the parent class in the inheritance hierarchy. The simple way to see this is using ancestors method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
What’s going on here? We’ve defined a base class Person
, a child class BusDriver
that inherits from Person
. We also defined a Insurable
module which we mixed into our BusDriver
class. When we call the ancestors
class method, we see the BusDriver
class first, then Insurable
module which was wedged between BusDriver
and Person
. Then goes the Person
class, then, obviously, Object
. This is all nice and clear.
But why do we see Kernel
between Object
and BasicObject
? This is because Kernel
is a module that is mixed into Object
thus inserting itself into the inheritance hierarchy. This ancestors
array is how the name resolution works throughout the inheritance chain.
Full search path
Now that you’ve seen the inheritance part of the name search, you can see the full picture:
1 2 |
|
const_missing method
When Ruby has finished searching the constants up the nesting and ancestors chain and didn’t find it, it gives the calling code the last chance by calling the const_missing method.
1 2 3 4 5 6 7 8 |
|
NameError
This error is called when Ruby can’t find the constant and there is no const_missing
method defined.
1 2 3 4 |
|
Word about autoloading
Let’s say you’d like to be flexible about your constants and load them automatically, following some naming convention? Turns out, there is a way, it’s called autoloading.
If we were to implememt autoloading from scratch, it would be something like this:
1 2 3 4 5 6 7 8 9 10 11 |
|
Turns out, we don’t have to, because autoloading is built into Ruby. We have Kernel#autoload, Module#autoload and more sophisticated ActiveSupport::Autoload. I’m not going to cover these topics here but will try to do it in a future post.
Ambiguity
Here comes the tricky part: what if you have multiple constants with the same name? Consider this example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Results might seem strange at first, but please remember the full search path:
1
|
|
First comes the lexical scope searching and only after the inheritance chain, where mixins are inserted between child and parent classes. Also, when Ruby finds a constant with a given name, it stops looking further.