Why you should nest modules in Ruby

Justin Toniazzo
Edited by thoughtbot

For the most part there’s no difference between these two ways of defining a class or module:

# nested
module Admin
  class User
  end
end

# inline
class Admin::User
end

The difference is often described as stylistic, but there are a couple of functional differences between the two approaches that you should be aware of before choosing one over the other.

To illustrate, imagine an app with User and Admin::User classes. Depending on how you reference those, you’ll get two different results:

module Admin
  class Nested
    def self.test = User.name
  end
end

class Admin::Inline
  def self.test = User.name
end

Admin::Nested.test # => "Admin::User"
Admin::Inline.test # => "User"

Here we use the Module#name, which just returns the name of the class/module, e.g. String.name returns "String" .

You can see how when referencing User from within an inline class, the class that’s ultimately looked up is the top-level User, even though you’re in an Admin namespace so you likely intended to be referencing Admin::User instead.

You can get around that by using the fully qualified ::Admin::User name:

class Admin::InlineQualified
  def self.test = ::Admin::User.name
end

Admin::InlineQualified.test # => "Admin::User"

This also affects inheritance. For example consider these two BaseController classes:

class BaseController
end

module Admin
  class BaseController
  end
end

As before, depending on how you reference them, you get two different results:

module Admin
  class NestedController < BaseController
  end
end

class Admin::InlineController < BaseController
end

Admin::NestedController.superclass # => Admin::BaseController
Admin::InlineController.superclass # => BaseController

Confusing!

Why does this happen?

The reason for this behavior has to do with how constants, like class and module names, are defined and referenced in ruby. You can see under the covers a bit by using the Module.nesting method.

module Admin
  class Nested
    def self.nesting = Module.nesting
  end
end

class Admin::Inline
  def self.nesting = Module.nesting
end

Admin::Nested.nesting # => [Admin::Nested, Admin]
Admin::Inline.nesting # => [Admin::Inline]

Here you can see that for the nested class, ruby is aware that the Admin::Nested constant is defined within an Admin namespace. Whereas with the inline class, ruby considers the Admin::Inline class to just be on its own.

It’s for this reason that, in the example above, the inline module returned User instead of Admin::User. When ruby is looking for where to find User, it will start by looking for that constant in the current namespace (e.g. looking for Admin::Inline::User), and then walk up the nesting tree until it encounters it. Since ruby does not consider the inline class to be nested under Admin, it doesn’t look for User in that namespace at all, and rather just goes straight to the top-level User.

Creating the outer module

The other difference between inline and nested module definitions is how the outer module is created. If I were to define a module like this, without anything else:

class One::Two
  # implementation
end

I’d get this error: uninitialized constant One (NameError) . This is because I’m attempting to reference the One constant before it’s defined.

This is why, especially in older apps, you’ll sometimes see empty module definitions like this:

module Api
end

This is because there’s some class defined like this:

class Api::UsersController
  # implementation
end

and since the Api module isn’t being defined there, it needs to be defined somewhere.

If instead it had been nested, it would work without the empty module:

module Api
  class UsersController
  end
end

For this one I should note that, if you’re working with a recent version of Rails, the framework will handle creating that outer module for you if it doesn’t exist already. You can just define a class Api::UsersController without having first defined module Api anywhere else. So in that respect at least this is likely not the biggest issue for you. But still it’s good to be aware that this is non-standard ruby behavior in case you run into it when writing scripts or something :)

Nesting is better

Because of these two differences, it’s better to just stick to nesting classes. You’ll pay a price in having to indent your code more, but you won’t have to worry about either of these gotchas, especially the first one. Rubocop even has a rule for this, making it super easy to add this to your CI pipeline.

If you’d rather use inline modules, it’s probably a good idea to stick to using the fully qualified name when referencing classes (e.g. Admin::User instead of User) to avoid any ambiguity.

Want to learn more?

Learn about the different services thoughtbot offers and how we can work together in streamlining your projects.