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
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.
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
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
Person. Then goes the
Person class, then, obviously,
Object. This is all nice and clear.
But why do we see
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:
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
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.
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:
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.