How You Nest Modules Matters in Ruby
Ruby provides two different syntaxes to nest modules (and classes):
# Syntax #1
module API
module V1
end
end
# Syntax #2
module API::V1
end
Syntax #2 requires that API
module already exists. Most Rubyists know that.
Besides this difference, they think that these two syntaxes are interchangeable.
With that line of thinking, what syntax to use for nesting modules turns out to
be a matter of preference.
But, the syntax you choose matters, which I will demonstrate shortly with examples that relate to how you would organize a versioned REST API.
Modules are just constants in Ruby, as such, regular constant look-up rules apply. The very first rule relates to how modules are nested. For the purpose of this blog post, we will ignore regular constants and just focus on modules.
When you reference a constant in a nested context, Ruby looks it up inside out:
module API
class Responder
end
module V1
class Controller
def action
Responder.respond_with('Hello, World!')
end
end
end
end
When #action
gets called, Ruby will see if Responder
is defined inside
API::V1::Controller
first, API::V1
second, API
third, and finally in the
top level. If it wasn't for this inside-out look-up, referencing Responder
as
above would not have worked. Instead, it would have to be referenced as
API::Responder
.
You can actually access the nesting information Ruby uses for this
look-up with Module.nesting
:
module API
class Responder
end
module V1
class Controller
p Module.nesting #=> [API::V1::Controller, API::V1, API]
def action
Responder.respond_with('Hello, World!')
end
end
end
end
Now, let's see how the look-up behavior changes when we mix in syntax #2:
module API
class Responder
end
end
module API::V1
class Controller
p Module.nesting #=> [API::V1::Controller, API::V1]
def action
Responder.respond_with('Hello, World!')
end
end
end
When #action
gets called, you will now get a NameError
:
NameError: uninitialized constant API::V1::Controller::Responder
This is because some nesting information gets lost when you use syntax #2.
You can see this from what Module.nesting
returns in the example above.
As per that example, Ruby now looks for Responder
inside API::V1::Controller
and API::V1
. It no longer looks inside API
, where Responder
is actually
defined hence the error.
Conclusion
The syntax you use for nesting modules in Ruby should not be a matter of preference. The syntax you use could be the difference between a working program, and the one that throws an error. So choose wisely. Having said that, I prefer syntax #1.
By the way, we've explored just one aspect of how constant look-up works in Ruby in general. If you use Rails, the matters gets even more complicated with the autoloading magic. The following references will help you learn more: