Nested Modules in Ruby

Nested Modules in Ruby

December 21, 2023

There are two different ways to define nested modules in Ruby. This post explains them both along with the differences between them and how to decide which one to use. We will also learn about the `Module.nesting` method, which returns the list of nesting for a module.

Modules are a very powerful feature of Ruby. Primarily, you use modules for two reasons: namespacing your code and sharing common methods across different classes (as mixins).

module Taggable
# this module holds all the code
# related to tagging items.
end

# module as namespace
module Blog
class Post
# add tagging functionality to Post class
include Taggable # module as mix-in
end

class Comment
# add tagging functionality to Comment class
include Taggable # module as mix-in
end
end

Using modules, you can create classes (or modules) with the same name without interfering with other classes. This allows you to design modular programs, breaking large components into smaller ones and also mixing and matching object behaviors.

Two Ways to Nest Modules in Ruby

You can nest multiple modules to create nested namespaces. There are two ways to define nested modules (or classes) in Ruby.

  • Define the second module inside the first one. If module Rails doesn't exist, Ruby will create it first, then define module ActiveJob. If module Rails already exists, then it will re-open it and define module ActiveJob inside it.
module Rails
module ActiveJob
end
end
  • Use the :: operator to define the second module on the same line.
module Rails::ActiveJob
end

There are three ways in which the second version (Rails::ActiveJob) differs from the first:

  1. It assumes that the outer module, i.e. Rails is already defined. If it's not, Ruby will throw a NameError: uninitialized constant Rails error. So use this syntax only if you're sure that module Rails exists.
  2. It allows you to reduce the amount of indentation. Instead of 3 levels of indentation, only one is necessary.
  3. You can not access the constants defined inside module Rails inside module ActiveJob without the scope resolution operator. This is a significant difference; the next section covers it in detail.

Accessing Constants in Nested Modules

The scope of constant access is different depending on how the nested module is defined. Let's consider the first version.

module Rails
VERSION = "7.1.2"

module ActiveJob
puts VERSION
end
end

# output
7.1.2

You can define both modules at two separate locations, and it should still work.

module Rails
VERSION = "7.1.2"
end

# somewhere else, after the above module is defined
module Rails
module ActiveJob
puts VERSION
end
end

# output
7.1.2

Let's try accessing the constant in a module defined using the second syntax. It throws a NameError. The reason: without any prefix, Ruby tries to access VERSION inside Rails::ActiveJob module, instead of the Rails module.

module Rails
VERSION = "7.1.2"
end

module Rails::ActiveJob
puts VERSION
end

# output
uninitialized constant Rails::ActiveJob::Version (NameError)

However, you can access it using the scope resolution operator (::) as follows:

module Rails
VERSION = "7.1.2"
end

module Rails::ActiveJob
puts Rails::VERSION
end

# output
7.1.2
Trying to access a constant defined in the parent module without :: operator fails with the second syntax.

The following example explains this point using classes and methods.

module Rails
VERSION = "7.1.2"

module ActiveJob
def version
VERSION
end
end
end

class Application
include Rails::ActiveJob
end

puts Application.new.version # 7.1.2

However, if you use :: to define Rails::ActiveJob without nesting it inside Rails, a NameError will be raised.

module Rails
VERSION = "7.1.2"
end

module Rails::ActiveJob
def version
VERSION
end
end

class Application
include Rails::ActiveJob
end

Application.new.version # uninitialized constant Rails::ActiveJob::VERSION (NameError)

💡To access a constant defined at the top-level namespace, prefix the constant with the scope-resolution operator, like `::VERSION`.

The 'Module.nesting' Method

The nesting method defined on the Module returns the list of modules nested at the time of call. Following example shows how the two versions differ in their module nesting.

module A
p Module.nesting # [A]

module B
p Module.nesting # [A::B, A]
end

module B::C
p Module.nesting # [A::B::C, A]
end
end

module A::D
p Module.nesting # [A::D]
end

When module D is defined, the nesting does not include module A. Hence, module D cannot access the constants defined in module A, without explicitly using the scope resolution (::) operator.

Sign up for my newsletter

Let's learn to become better developers.

Comments

No comments yet. Be the first to leave one.

Sign in to leave a comment.