Nested Modules in Ruby

Nested Modules in Ruby

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.

3 min read

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.


That's a wrap. I hope you found this article helpful and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.