How You Nest Modules Matters in Ruby

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: