Excerpt

At a high level, Ruby modules provide the following key benefits:
-
Namespaces: Allows you to namespace, organize your objects, and/or nest objects.
-
Multiple Inheritance: Provides multiple inheritance to add behavior in the form of extra methods to existing objects either at the class and/or instance level.
-
Functions: Encapsulates purely functional methods which can be messaged directly like any other Ruby object.
This article will spend time exploring this feature set so let’s dive in!
- Namespaces
- Naming
- Conclusion
## Object Hierarchy
To understand where modules fit within the larger Ruby object hierarchy, the following visualization will help:

Ruby Object Hierarchy
The above can also be achieved via the console:
```plain text
Module.ancestors.reverse
# [
# BasicObject,
# Kernel,
# Object < BasicObject,

At a high level, Ruby modules provide the following key benefits:
-
Namespaces: Allows you to namespace, organize your objects, and/or nest objects.
-
Multiple Inheritance: Provides multiple inheritance to add behavior in the form of extra methods to existing objects either at the class and/or instance level.
-
Functions: Encapsulates purely functional methods which can be messaged directly like any other Ruby object.
This article will spend time exploring this feature set so let’s dive in!
- Namespaces
- Naming
- Conclusion
## Object Hierarchy
To understand where modules fit within the larger Ruby object hierarchy, the following visualization will help:

Ruby Object Hierarchy
The above can also be achieved via the console:
```plain text
Module.ancestors.reverse
# [
# BasicObject,
# Kernel,
# Object < BasicObject,
# Module < Object
# ]
Class.ancestors.reverse
# [
# BasicObject,
# Kernel,
# Module < Object,
# Class < Module
# ]
```
You can also think of modules and classes as:
-
Functional (modules)
-
Object Oriented (classes)
Granted, nearly everything in Ruby is an object — modules included — but this is a useful framing.
## Namespaces
Modules are the primary way to namespace and organize objects within your architecture. Example:
```plain text
module Models
Location = Data.define :latitude, :longitude
end
```
You can nest classes within each other but is a Nested Classes Antipattern that is strongly discouraged unless, in rare situations, you need a private inner class.
You can also have nested namespaces for further organization:
```plain text
module One
module Two
module Models
Location = Data.define :latitude, :longitude
end
end
end
```
The above is the recommended way to nest modules by simply indenting them. Alternatively, you can use a flat structure but is a Flat Modules Antipattern which is strongly discouraged. Example:
```plain text
module One::Two::Models
Location = Data.define :latitude, :longitude
end
```
Due to the flexibility of modules as namespaces, you have the ability to quickly inspect/debug the hierarchy by printing out the nesting:
```plain text
module One
module Two
module Models
puts Module.nesting
Location = Data.define :latitude, :longitude
end
end
end
# One::Two::Models
# One::Two
# One
```
The above, minus the puts statement, is the correct way to use modules to organize and give structural meaning to your code.
## Hooks
Hooks allow for additional behavior when a module is used within an object. These also open up advanced functionality like metaprogramming. There are four primary hooks:
-
.extended: Extends class methods. Takes a single argument: descendant.
-
.included: Includes instance methods. Takes a single argument: descendant.
-
.prepended: Prepends methods before your prepended object by making the object a superclass instead of the subclass. Takes a single argument: ancestor.
-
.using: Refines existing methods or injects new methods altogether. Takes a single argument: target. The target is the object which will be refined. Refinements are outside the scope of this article but check out my Refinements article for a deeper dive.
Visually, the above is:
```plain text
module Demo
def self.extended(descendant) = super
def self.included(descendant) = super
def self.prepended(ancestor) = super
end
Class.new.extend(Demo).singleton_class.ancestors
# [
# #<Class:#<Class:0x0000000130c101e8>>,
# Demo,
# #<Class:Object>,
# #<Class:BasicObject>,
# Class,
# Module,
# Object,
# Kernel,
# BasicObject
# ]
Class.new.include(Demo).ancestors
# [
# #<Class:0x0000000130c58038>,
# Demo,
# Object,
# Kernel,
# BasicObject
# ]
Class.new.prepend(Demo).ancestors
# [
# Demo,
# #<Class:0x00000001222123c0>,
# Object,
# Kernel,
# BasicObject
# ]
```
To break this down further, a few notes:
-
The anonymous class (via Class.new) is for demonstration purposes as represented by #<Class:0x00*>.
-
Use of descendant and ancestor improves readability so you can quickly understand kind of object you are extending, including, and/or prepending.
-
When extending and including, Demo is always the superclass. Only when prepending, is Demo the subclass. This is also why descendant is used when including/extending while ancestor is used when prepending to help remind you of this hierarchy. Use this terminology to ensure your own code remains readable.
💡 Semi-related, the Class.inherited hook should also use descendant as an argument because when a subclass inherits from a parent class, the subclass is passed in as the descendant.
```plain text
class Parent
def self.inherited(descendant) = super
end
```
💡 When including modules — and using the same implementation as above — there are multiple ways to check if functionality is included in your class via the following:
```plain text
Example.include? Demo # => true
Example < Demo # => true
Example.ancestors.include? Demo # => true
```
### Appending Features
If we drop down a level, there is the Module#append_features which is called by Module#include. If you implement this method, you can be good or evil. Here’s an evil situation in which #append_features is abused:
```plain text
module Demo
def self.append_features descendant
descendant.module_eval do
def label = "😈"
end
end
end
Example = Class.new { include Demo }
Example < Demo # nil
Example.new.label # "😈"
```
With the above, we are using #append_features which is called by #include to alter behavior by dynamically creating a #label method on the descendant object. This changes #include behavior by not applying default functionality — by not calling super — as found when messaging #include directly. Don’t do this!. Hiding who defined the #label method causes major pain when debugging.
When used for good, you can leverage #append_features to add additional instrumentation, debugging, control checks, etc. Here’s an example where #append_features is used for good by logging what has been included for debugging purposes:
```plain text
module Demo
def self.append_features descendant
puts "Including #{name.inspect} in #{descendant.name.inspect}."
super
end
end
class Example
include Demo
end
# Including "Demo" in "Example".
```
— Uncle Ben, Spiderman
Be good, not evil.
## Multiple Inheritance
Multiple inheritance — also known as mixins — allows you to include, extend, and/or prepend common functionality to multiple objects (as mentioned in the Hooks section above). Specifically, the extraction of methods to a common module which creates an implicit assumption that all objects, which make use of this common module, can rely on the same data and/or dependent methods. Example:
```plain text
module Nameable
def full_name = [first_name, last_name].compact.join " "
end
User = Data.define :first_name, :last_name do
include Nameable
end
User[first_name: "Jayne", last_name: "Doe"].full_name
# Jayne Doe
```
Notice how the Nameable module assumes there is a #first_name and #last_name method. This implicit contract assumes you’ll be using Nameable in an object that has these methods. Taken to extremes — with multiple methods in a single module — you’ll end up in a situation where it’s hard to refactor/untangle this mess especially when objects start to diverge from each other when new behavior is added.
This is also why so many teams get themselves in trouble by creating complex implementations due to liberal use of multiple inheritance. For example, let’s say you split up behavior across five modules or more and end up with an object like this:
```plain text
class Demo
include Comparable
include Geolocatable
include Indexable
include Nameable
include Observable
include Searchable
end
```
The above is definitely extreme but I’ve seen this happen on multiple occasions. There are a couple major issues with the above implementation:
-
Confusing Behavior: The Demo class has way too much behavior due to being able to be compared, geolocatable, indexed, etc. In other words, someone slathered on a bunch of behavior — most likely out of convenience and laziness — while not taking the time to think about what this objects’s sole purpose is. Once you know that, then you can split up behavior into distinct objects which adhere more to the Single Responsibility Principle as defined in the S of SOLID design.
-
Order Matters: You’ll notice, I included each module alphabetically but that might not be the right order especially if some of the modules have the same method as the previous module(s). In other words, any module included after a previous module takes precedence which means you could have conflicting behavior which, if not careful, can lead to needlessly complicated debugging situations.
The most egregious is Active Support Concerns which encourages multiple inheritance by default. Worse, you can no longer use plain Ruby functionality because concerns are a specific Rails dialect which means you have to work harder when attempting to extract functionality to a library, gem, etc. for reuse across multiple projects. Stick with plain Ruby, because you have the power to more freely refactor as necessary.
When used sparingly, multiple inheritance can be powerful but extracting multiple methods to a common module only because you want to reduce the lines of code in existing objects is never a wise move. Instead, consider extracting functionality via the Command Pattern and then injecting (i.e. Dependency Injection) what you need when you need it. Composition over inheritance is far more advantageous to maintainable code in the long run. The more advanced Module Builder Pattern can be another solution as well.
## Functions
Another advantage of modules is creating a logical group of functional methods. This is handy for situations where you want MyModule.my_function, for example, for purely functional reasons because you have a collection of functions that don’t require state or additional behavior so grouping them within a module for that purpose makes logical sense.
You can do this using extend self and #module_function, while still having the option to include, extend, and/or prepend your module of functions. Each is explained below.
### Extending Self
Extending self allows you to turn what would normally be instance methods into class methods. Example:
```plain text
module Demo
extend self
def label = "Demo"
end
Demo.label # "Demo"
Demo.singleton_methods.include? :label # true
```
Visibility doesn’t change when included in a class either:
```plain text
MyClass = Class.new { include Demo }
my_class = MyClass.new
my_class.label # "Demo"
my_class.public_methods.include? :label # true
MyClass.singleton_methods.include? :label # false
```
As you can see, when the Demo module is included in MyClass, the #label method is an instance method and remains public.
Additionally, any of these methods can be overridden. Example:
```plain text
module Demo
extend self
def label = "Demo"
end
Demo.label # "Demo"
module Demo
def label = "Override"
end
Demo.label # "Override"
```
Notice, when we redefined the original method’s behavior, we got the overridden behavior instead of the original behavior. We’ll come back to this when talking about module functions next.
Specifying all methods in your module as functions allows you to have similar behavior when extending self but with the added benefit of changing visibility for better encapsulation. Example:
```plain text
module Demo
module_function
def label = "Demo"
end
Demo.label # "Demo"
Demo.singleton_methods.include? :label # true
```
You’ll notice behavior is identical to when we were extending self earlier but visibility changes when included in a class:
```plain text
MyClass = Class.new { include Demo }
my_class = MyClass.new
my_class.label # `NoMethodError`
my_class.private_methods.include? :label # true
my_class.public_methods.include? :label # false
MyClass.singleton_methods.include? :label # false
```
Notice the #label method is now a private method. This has the added benefit of ensuring proper encapsulation versus extend self.
Another aspect of module functions is, once included, they are copies of original behavior. Example:
```plain text
module Demo
module_function
def label = "Demo"
end
Demo.label # "Demo"
module Demo
def label = "Override"
end
Demo.label # "Demo"
module Demo
module_function
def label = "Override"
end
Demo.label # "Override"
```
Notice how, when attempting to override original functionality, we only got the original behavior. This is because including module functions causes them to be copies of the originals. Only when overriding the method and using module_function in the last example, we see the override work. This has the added benefit of not causing side effects where using extend self with an override would mutate behavior which might be surprising and unexpected.
Prefer using module_function instead of extend self when including, extending, and/or prepending your module because your Object API footprint should remain small (private) instead of large (public) which gives you the power to refactor more easily while minimizing access to data that other objects shouldn’t be aware of or allowed to reach. Only when you discover a need for public use, should you make a method public.
💡 Use of module_function works much like protected or private in that you can define it once and all methods below will be module functions or you can supply a symbol to specify which function you want to be a module function. Example: module_function :label.
By default, modules don’t store state which is why they are great for organization (namespaces), multiple inheritance, or being a collection of purely functional behavior. That said, the Module Builder Pattern provides a way to get the best of both worlds by having an object that is both a class and module at the same time for maximum functionality and reusability. Diving into the Module Builder Pattern is outside the scope of this article so follow the link to learn more.
## Naming
Naming can be challenging but is important. As discussed above, there are two categories to consider when naming your modules:
1.
Nouns: Namespaces for organization.
2.
Adjectives: Interfaces which serve as a contract between different parts of the system by specifying what operations can be performed and how they should be invoked. These interfaces use multiple inheritance, via hooks, to provide behavior that adhere to the interface.
For nouns, stick with plural nouns like: Parsers, Processors, Handlers, Clients, etc. The reason being is you are organizing multiple objects of similar behavior within the same namespace so pluralization makes logical sense.
For adjectives, suffix your module with -able. There is plenty of precedence for this within the core Ruby libraries, for example:
If it helps illustrate further, I follow these conventions in my own gems too:
Consistency is key.
## Conclusion
While this is by no means exhaustive of what modules can do for you, I hope it’s been empowering. As you architect your own code, take time to give careful thought to naming, organization, objects versus methods, inheritance, composition, etc. in your own design so your code is easy to discover, understand, and use.