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 PI = 3 . 1415 MINUTES_IN_ONE_HOUR = 60 LOOK_MA = "I'm a constant!" module A end class Person end module Screen::Widget::Button end

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 A A_CONSTANT = 'I am defined in module A' module B module C def self . inspect_nesting puts Module . nesting . inspect puts A_CONSTANT end end end end A : :B :: C . inspect_nesting # => [A::B::C, A::B, A] # => I am defined in module A

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 module Screen DEFAULT_RESOLUTION = [ 1024 , 768 ] module Widgets module MacOS end end end # Alternative syntax module Screen::Widgets::MacOS::Button def self . inspect_nesting puts Module . nesting . inspect puts DEFAULT_RESOLUTION end end Screen : :Widgets :: MacOS : :Button . inspect_nesting # => [Screen::Widgets::MacOS::Button] NameError : uninitialized constant Screen : :Widgets :: MacOS : :Button :: DEFAULT_RESOLUTION from ( irb ): 26 :in `inspect_nesting' from (irb):29

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 class Person DRIVING_LICENSE_AGE = 18 end class BusDriver < Person def can_drive_from DRIVING_LICENSE_AGE end end bus_driver = BusDriver . new puts bus_driver . can_drive_from # => 18

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 module Insurable LIFE_INSURANCE_AMOUNT = 150_000 end class Person DRIVING_LICENSE_AGE = 18 end class BusDriver < Person include Insurable def can_drive_from "Can drive from #{ DRIVING_LICENSE_AGE } , with life insurance of $ #{ LIFE_INSURANCE_AMOUNT } " end end puts BusDriver . ancestors . inspect puts BusDriver . new . can_drive_from # => [BusDriver, Insurable, Person, Object, Kernel, BasicObject] # => Can drive from 18, with life insurance of $150000

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 # searching from left to right full_path = [ Module . nesting + Module . ancestors ]. uniq

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 module Person def self . const_missing ( name ) puts "Oh me oh my, can't find the constant: #{ name } " end end Person : :LOL # => Oh me oh my, can't find the constant: LOL

NameError

This error is called when Ruby can’t find the constant and there is no const_missing method defined.

1 2 3 4 Object : :BLASTER # => NameError: uninitialized constant BLASTER from ( irb ): 8

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 def Object . const_missing ( name ) @looked_for ||= {} str_name = name . to_s raise "Class not found: #{ name } " if @looked_for [ str_name ] @looked_for [ str_name ] = 1 file = str_name . downcase require file klass = const_get ( name ) return klass if klass raise "Class not found: #{ name } " end

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 module Insurable LIFE_INSURANCE_AMOUNT = 150_000 end class Person LIFE_INSURANCE_AMOUNT = 50_000 end class Pilot < Person INSURANCE_AMOUNT = 300_000 include Insurable end puts Pilot : :INSURANCE_AMOUNT puts Pilot : :LIFE_INSURANCE_AMOUNT # => 300_000 # => 150_000

Results might seem strange at first, but please remember the full search path:

1 [ Module . nesting + Module . ancestors ]. uniq

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.