If you have ever worked with Ruby or Ruby on Rails, then you have probably noticed a certain trend. Some people declare nested classes within modules in a single line while others use multiple lines with the module/class reserved words.
By default, the Rubocop linter suggests using the far lengthier way of declaring a class but doesn’t explain why. Let’s take a closer look.
Nested Style
We’ll refer to the first, more verbose way of declaring nested classes/modules as nested style.
Let’s start exploring how nested style works by writing some code into the Ruby interpreter and running the irb
command in the terminal.
This piece of code defines the User class with an initializer that receives a variable number of keyword arguments. Those keyword arguments are stored as a Hash in the instance variable @attributes
. They can be accessed via the instance method attributes
.
Now that we have the User class, let’s create a class that will handle creating a User and doing other stuff (e.g. showing the message ‘Welcome <name>’).
This works great, but what if we add another User
class inside the Operations
module?
Let’s call the Operations::UserCreator.call
now and see what happens:
What does this have to do with nested vs. compact style?
To answer that question, we’ll look at the compact style and how it behaves differently compared to the nested style.
Compact Style
Let’s close the Ruby interpreter and reopen it by typing exit
and executing irb
.
After that, we’ll paste the User
class and the Operations::User
class
We’ll now declare the Operations::UserCreator
class by using the compact style and execute the .call
method:
Uh-oh! We hit an error. It’s telling us that the constant Operations
is not declared.
When using Compact Style, we must declare the parent constants first. So, let’s fix this issue:
The difference is subtle, but notice how this Compact style creates an Object of the User
class, and the Nested style creates an Object of the Operations::User
class.
It’s all about Lexical Scope and Constants lookup.
Lexical scope is the visibility and accessibility of variables and methods within a particular block of code based on their definition and the nesting structure of the code.
In this case, the nesting of the class is not the same when using compact or nested:
As you can see, when using Compact style, the lexical scope (or the scope in which the code is executed) is Operations::UserCreator
, without any other nesting.
When a constant is looked up, it will look up in the following scopes:
Operations::UserCreator
top-level
scope (think of it as the root node of the constants tree)
On the other hand, when using the nested style, the lexical scope is [Operations::UserCreator, Operations]
.
This time, when a constant is looked up, it will look up in the following scopes instead:
Operations::UserCreator
Operations
top-level
scope
Summing up
Using nested or compact styles when declaring classes/modules in Ruby impacts scopes, code execution and constant lookup.
Nested style
Pros
- Namespacing: The nested style allows for the logical grouping of classes within a module or outer class. This helps in organizing related classes and avoids naming conflicts.
- Improved readability: Nesting can provide context and improve code readability when classes are closely related.
Cons
- Increased indentation: Nesting classes result in increased indentation levels, which can make the code less readable if the nesting becomes too deep.
Compact style
Pros
- Simplicity: The compact style is straightforward and easy to read. It keeps the class declaration concise and separate from other modules or classes.
- Avoids deep nesting: Using a compact style avoids excessive nesting, which can make code harder to understand and maintain.
Cons
- Potential name clashes: If you have multiple classes with the same name defined in different places, there could be naming conflicts. However, proper namespacing can mitigate this issue.