In Ruby, you have a choice: define classes or modules either inline or nested within another module. While both approaches achieve similar results, there are key functional differences to consider, making nesting the preferred approach.
The Subtle Distinction in Referencing
Let’s look at an example using User and Admin::User classes:
module Admin class Nested # Nested approach def self.test = User.name end end class Admin::Inline # Inline approach def self.test = User.name end puts Admin::Nested.test # => "Admin::User" (correct) puts Admin::Inline.test # => "User" (incorrect)
Here, Admin::Nested correctly references Admin::User using User.name. However, Admin::Inline unexpectedly returns just “User”. This is because Ruby’s constant lookup prioritizes the current namespace first. Since Admin::Inline isn’t explicitly nested under Admin, it doesn’t search within that namespace for User.
To achieve the intended behavior with inline classes, you’d need to use the fully qualified name:
class Admin::InlineQualified def self.test = ::Admin::User.name # Using fully qualified name end puts Admin::InlineQualified.test # => "Admin::User" (correct)
Inheritance Nuances
The same concept applies to inheritance. Consider these BaseController classes:
class BaseController end module Admin class BaseController # Nested approach end end puts Admin::NestedController.superclass # => Admin::BaseController (correct) puts Admin::InlineController.superclass # => BaseController (incorrect)
Similar to the previous example, Admin::NestedController accurately retrieves its superclass Admin::BaseController. However, Admin::InlineController mistakenly returns the top-level BaseController.
Understanding Nesting’s Benefits
Nesting offers several advantages:
- Clarity and Explicitness: Nested modules clearly indicate the hierarchy and relationships between modules and classes.
- Correct Constant Resolution: Nesting ensures Ruby searches for constants within the appropriate namespace, preventing unexpected behavior.
- Automatic Module Creation: When defining a nested class like
Api::UsersController, Ruby automatically creates theApimodule if it doesn’t exist.
Recommendations and Best Practices
While inline modules can work with extra care (using fully qualified names), nesting is generally recommended for its clarity, predictability, and adherence to Ruby conventions. Static code analysis tools like Rubocop can enforce this practice, making it easy to integrate into your development workflow.
In Summary
By embracing nesting in Ruby modules, you gain a more organized, predictable, and robust codebase, avoiding potential gotchas and ensuring your code behaves as intended.