Namespaced Classes in Rails
In larger Rails applications, it is common to move related models, controllers, and/or views into sub-directories. Once moved, those classes will no longer be automatically found by Rails’ autoloader. There are two solutions:
-
Place the classes into a namespace (module) whose name maps to the directory name.
I want to focus on the second option here.
In Ruby, there are two main ways to define a class inside a module.
The “standard” way is to nest the class definition inside the module:
The “shortcut” way is to use the ::
notation:
Both of these approaches generally work, but they are not equivalent.
The main difference is related to constant lookup. In the standard
method, the code in MyClass
can refer to other names in MyModule
without an explicit namespace qualifier; Ruby will be able to resolve
those references. Using the shortcut method, those references will
have to be prefixed with MyModule::
, because Ruby will not look
inside MyModule
for those names.
This is explained very well by Conrad Irwin in his excellent post, Everything you ever wanted to know about constant lookup in Ruby. It is also covered quite elegantly by Avdi Grimm in Ruby Tapas Episode 158. This episode is only available to subscribers, though, so you may not be able to watch it.
For this reason alone, I recommend using the standard approach. However, there are other considerations as well.
In order to use the shortcut approach in Ruby (without Rails),
MyModule
must already be defined when this class definition is
loaded. Otherwise, Ruby will raise a NameError
because it doesn’t
know what MyModule
is. This is another strong argument in favor of
the standard approach in plain Ruby. Rails blurs this situation a bit
however, because the autoloader will sometimes define MyModule
for
you. More on that in a later post.
Speaking of the autoloader, we need to be sure that it will continue to find these nested classes using either type of definition. For a good overview of how the autoloader works, see urbanautomaton’s excellent blog post, Rails autoloading — how it works, and when it doesn’t. It turns out that there are no major concerns on this front; the autoloader handles both approaches just fine.
In my brief experience with Rails, I’ve seen more of the shortcut notation than the standard notation. I’ve even seen the following pattern when using single-table inheritance:
The shortcut notation is hiding what’s really going on here: we’re
defining a subclass of Vehicle
as a nested class inside the
Vehicle
class. This is almost certainly not what was intended.
Using the standard form of definition makes this much more obvious.
I’ve seen other code where the namespace and the base class are not the same class, but the namespace is another class in the system. Again, I suspect that this was not intentional, but the shortcut notation hid the problem.
For this example, I’d prefer one of the following solutions:
In this case, the base class is also nested in the namespace, so it
ends up in the same directory as its subclasses, which is nice.
Having to refer to it as Vehicles::Vehicle
everywhere is a bit of a
pain, though, especially in relations. It doesn’t affect the
underlying database table name though, because
enclosing modules are not considered when inferring table names.
The major downside to the standard approach to defining namespaced classes is that all of the code ends up indented an extra level for each level of namespace. This can be a bit ugly. It is possible to use the following pattern to get around that problem:
This works, but is much less “scannable”. It’s surprising to see two definitions on one line. I think that having the extra indentation is more palatable than this technique.
In summary, I recommend that namespaced classes always be defined
using the standard method in both Ruby and Rails apps. The shortcut
approach invites problems with constant lookup, extra namespace
prefixes, NameError
s in Ruby, and unintentional class nesting.