In Progress
Unit 1, Lesson 1
In Progress

# Constant Lookup Scope

Video transcript & code

Let's say we have a file that constants for some vital stats on planets in the solar system.

``````module Planets
module Jupiter
APHELION_MILES = 507_363_000
end
end
``````

We can reference these constants wherever we need, using their fully-qualified names.

``````require "./planets"
Planets::Jupiter::APHELION_MILES # => 507363000
``````

Now let's say we decide we also want these stats in metric form. We add a new file, `metric.rb`, which re-opens the `Planets` module and adds metric versions of the numbers.

``````module Planets::Jupiter
APHELION_KM = 816_520_800
end
``````

Once again, we can reference these constants wherever we want.

``````require "./planets"
require "./metric"
Planets::Jupiter::APHELION_KM    # => 816520800
``````

Next, we get the bright idea that rather than hardcoding kilometers, we can simply convert the original mile units into kilometers. Back in the `metric.rb` file we add a conversion factor to the top-level `Planets` module, and redefine the metric stats in terms of this new constant.

``````module Planets
MILES_TO_KM = 1.60934
end

module Planets::Jupiter
APHELION_KM = (APHELION_MILES * MILES_TO_KM).round
end
``````

But now when we try to load these files and use the constants, we get a surprise failure:

``````require "./planets"
require "./metric2"
Planets::Jupiter::APHELION_KM    # =>
# ~> /home/avdi/Dropbox/rubytapas/158-constant-lookup-scope/metric2.rb:7:in `<module:Jupiter>': uninitialized constant Planets::Jupiter::MILES_TO_KM (NameError)
# ~>    from /home/avdi/Dropbox/rubytapas/158-constant-lookup-scope/metric2.rb:6:in `<top (required)>'
# ~>    from /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'
# ~>    from /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'
# ~>    from -:2:in `<main>'
``````

It seems this code can't find the `MILES_TO_KM` constant we just defined.

In defining the metric constants, we've used a syntactical shortcut to reopen the `Jupiter` module. Rather than using two nested module declarations, we've used a single module declaration with the fully-qualified module name.

Let's try switching this to the longer nested form.

``````module Planets
MILES_TO_KM = 1.60934
end

module Planets
module Jupiter
APHELION_KM = (APHELION_MILES * MILES_TO_KM).round
end
end
``````

Suddenly, the code starts to work.

``````require "./planets"
require "./metric3"
Planets::Jupiter::APHELION_KM    # => 816519570
``````

So what's going on here? In order to get some insight into how these two forms of module declaration differ, we'll use a special method Ruby defines for introspecting the current constant lookup scope. We'll start with a long-form nested module declaration. Inside the declaration, we examine the return value of `Module.nesting`. This method shows us the list of places that Ruby will look for a constant within the current scope.

``````module Planets
module Jupiter
Module.nesting              # => [Planets::Jupiter, Planets]
end
end
``````

As we can see, the constant lookup chain consists of two locations: first, Ruby will look in the `Jupiter` module, and if the constant is not found there, it will then look in the `Planets` module.

Ruby actually omits one final implicit final stop in the lookup chain. If we define a constant at the top level, outside of any class or module, it is added to the `Object` class. It can then be found inside our nested module, even though `Module.nesting` doesn't show it.

``````ANSWER = 42
module Planets
module Jupiter
Module.nesting              # => [Planets::Jupiter, Planets]
end
end
``````

Now let's look at `Module.nesting` inside a shorthand module declaration.

``````module Planets
module Jupiter
Module.nesting              # => [Planets::Jupiter, Planets]
end
end

module Planets::Jupiter
Module.nesting                # => [Planets::Jupiter]
end
``````

As we can see, this time the lookup path contains only the `Jupiter` module, not the containing `Planets` module. This is because Ruby determines the lookup chain based only on the lexical scoping of the current code. It does not take into consideration the containing modules or classes of the current class or module. This might come as a surprise. It certainly came as a surprise to me, the first time I found out about it.

In keeping with the Principle of Least Astonishment, I feel that this is a good argument for preferring the fully nested form of module and class declarations to the shorthand version. But this is not the only reason to prefer the longer form. The shorthand form is also problematic when it comes to load order. If we switch the order the files are loaded in, we see an error:

``````module Planets::Jupiter
Module.nesting                # =>
end

module Planets
module Jupiter
Module.nesting              # =>
end
end
# ~> -:1:in `<main>': uninitialized constant Planets (NameError)
``````

This is because the shorthand form will only create the module at the tail end of the module nesting. It won't automatically create any intermediary modules if they don't already exist. This limitation is unavoidable, because it has no way of knowing if the intervening missing constants should be declared as modules or as classes.

The takeaway from all this is simple: there is no reason that I can think of to ever use the shorthand form of module declaration. If we choose, instead, to always declare each level of nesting explicitly, we avoid surprises.

Before I go, I'd like to thank Conrad Irwin, whose article "Everything you ever wanted to know about constant lookup in Ruby" helped inspire this episode.

Happy hacking!