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.