Back to Scaling Ruby Applications guides

A Beginner's Guide to Ruby Modules and Mixins

Stanley Ulili
Updated on September 3, 2025

Ruby modules are one of the language's most powerful features for organizing code and sharing functionality between classes. Unlike classes, modules cannot be instantiated directly, but they provide essential mechanisms for namespacing, code reuse, and implementing multiple inheritance through mixins.

Understanding modules is essential for writing maintainable Ruby code. They address common issues like namespace pollution, code duplication, and the constraints of single inheritance. Ruby's module system provides elegant solutions for sharing behavior across unrelated classes while keeping your code organized.

In this tutorial, you'll understand how to leverage modules to write more modular, reusable Ruby code that's easier to maintain and extend.

Prerequisites

This guide assumes that Ruby is installed and that you are familiar with fundamental Ruby concepts such as classes, methods, and inheritance. The examples provided are compatible with Ruby 2.7 and later versions, although most of the concepts are applicable to earlier Ruby versions too.

What are Ruby modules?

Ruby modules serve two primary purposes in the language: they act as namespaces to organize related code and provide mixins to share functionality between classes. This dual role makes them indispensable for building well-structured applications.

To get started with Ruby modules, let's create a project directory to organize our examples:

 
mkdir ruby-modules-tutorial && cd ruby-modules-tutorial

Here's a simple example that demonstrates the core concept of mixins:

basic_module.rb
module Greetings
  def say_hello
    puts "Hello from module!"
  end

  def say_goodbye
    puts "Goodbye from module!"
  end
end

class Person
  include Greetings
end

person = Person.new
person.say_hello
person.say_goodbye

This example defines a Greetings module containing two methods. The Person class uses include to mix in these methods, making them available to all Person instances. Unlike inheritance, you cannot instantiate modules directly - calling Greetings.new would raise an error.

 
ruby basic_module.rb
Output
Hello from module!
Goodbye from module!

The output confirms that the Person instance can access methods from the module. This demonstrates how modules enable code sharing without the constraints of single inheritance.

Let's explore a more practical example that shows how modules work in real applications.

Creating your first module

Modules become particularly powerful when they contain related functionality that multiple classes can utilize. Here's a practical example that demonstrates common module patterns:

math_operations.rb
module MathOperations
  PI = 3.14159

  def square(number)
    number * number
  end

  def cube(number)
    number * number * number
  end

  def circle_area(radius)
    PI * square(radius)
  end
end

class Calculator
  include MathOperations

  def calculate_volume(radius, height)
    circle_area(radius) * height
  end
end

calc = Calculator.new
puts "5 squared: #{calc.square(5)}"
puts "3 cubed: #{calc.cube(3)}"
puts "Circle area (radius 4): #{calc.circle_area(4)}"
puts "Cylinder volume: #{calc.calculate_volume(3, 10)}"

This module showcases several key concepts: constants are shared with including classes, methods within the module can call each other, and classes can build upon module functionality with their own methods. The PI constant becomes available to any class that includes the module, while methods like circle_area can leverage other module methods like square.

 
ruby math_operations.rb
Output
5 squared: 25
3 cubed: 27
Circle area (radius 4): 50.26544
Cylinder volume: 282.7431

The Calculator class gains access to all mathematical operations and uses them to implement more complex calculations. This pattern promotes code reuse while maintaining clean separation of concerns.

Beyond mixins, modules excel at solving naming conflicts through namespacing.

Using modules as namespaces

As applications grow, class name conflicts become inevitable. Modules provide namespacing to prevent these collisions while keeping related classes organized together:

namespaces.rb
module Animals
  class Dog
    def speak
      puts "Woof!"
    end
  end

  class Cat
    def speak
      puts "Meow!"
    end
  end
end

module Robots
  class Dog
    def speak
      puts "Beep beep!"
    end
  end
end

# Access classes through their namespaces
animal_dog = Animals::Dog.new
robot_dog = Robots::Dog.new

animal_dog.speak
robot_dog.speak

# You can also access them this way
cat = Animals::Cat.new
cat.speak

The scope resolution operator (::) allows you to specify exactly which Dog class you want to use. This eliminates ambiguity and enables you to have multiple classes with the same name in different contexts.

 
ruby namespaces.rb
Output
Woof!
Beep beep!
Meow!

Both Dog classes coexist peacefully, each responding according to their domain context. This namespacing approach scales well as your application grows and incorporates multiple domains or third-party libraries.

Ruby provides three distinct ways to incorporate modules into classes, each serving different purposes.

Understanding include, extend, and prepend

Ruby offers three methods for mixing modules into classes: include, extend, and prepend. Understanding when to use each is crucial for effective module design:

mixing_methods.rb
module Speakable
  def greet
    puts "Hello from #{self.class}"
  end
end

class Person
  include Speakable
end

class Robot
  extend Speakable
end

# include makes module methods available to instances
person = Person.new
person.greet

# extend makes module methods available to the class itself
Robot.greet

# This won't work - Robot instances don't have access
# robot = Robot.new
# robot.greet  # This would raise an error

The key distinction: include adds methods to class instances, while extend adds them to the class itself. This means Person objects can call greet, but only the Robot class (not Robot instances) has access to the method.

 
ruby mixing_methods.rb
Output
Hello from Person
Hello from Robot

Both approaches successfully add the greet method, but they target different recipients. Choose include when you want to add behavior to objects, and extend when you want to add class-level functionality.

The third option, prepend, offers unique capabilities for method wrapping and decoration.

Working with prepend

The prepend method resembles include but alters method lookup order, enabling powerful wrapper patterns:

prepend_example.rb
module Loggable
  def process_data
    puts "Logging: Starting data processing"
    super
    puts "Logging: Finished data processing"
  end
end

class DataProcessor
  prepend Loggable

  def process_data
    puts "Processing the actual data"
  end
end

class SimpleProcessor
  include Loggable

  def process_data
    puts "Simple processing"
    super rescue puts "No super method found"
  end
end

puts "With prepend:"
DataProcessor.new.process_data

puts "\nWith include:"
SimpleProcessor.new.process_data

With prepend, the module's method is called first and can invoke the class's method via super. This creates a clean wrapper pattern where the module can add behavior before and after the main functionality.

 
ruby prepend_example.rb
Output
With prepend:
Logging: Starting data processing
Processing the actual data
Logging: Finished data processing

With include:
Simple processing
Logging: Starting data processing
No super method found

The prepend approach allows the logging module to wrap around the core processing logic seamlessly. This pattern is particularly useful for cross-cutting concerns like logging, authentication, or performance monitoring.

Modules can include more than just methods; they support complex organizational structures through constants and nesting.

Module constants and nested modules

Modules excel at creating hierarchical code organization through constants and nested modules:

module_organization.rb
module API
  VERSION = "1.0.0"
  BASE_URL = "https://api.example.com"

  module Authentication
    TOKEN_EXPIRY = 3600  # seconds

    def generate_token
      "token_#{Time.now.to_i}"
    end
  end

  module Endpoints
    USERS = "/users"
    POSTS = "/posts"

    def build_url(endpoint)
      "#{API::BASE_URL}#{endpoint}"
    end
  end

  class Client
    include Authentication
    include Endpoints

    def initialize
      puts "API Client v#{API::VERSION} initialized"
      puts "Token expires in #{Authentication::TOKEN_EXPIRY} seconds"
    end

    def get_users
      token = generate_token
      url = build_url(USERS)
      puts "GET #{url} with token: #{token}"
    end
  end
end

client = API::Client.new
client.get_users

This structure demonstrates how modules create logical groupings of related functionality. Constants provide configuration, nested modules organize related methods, and the scope resolution operator enables precise access to any component.

 
ruby module_organization.rb
Output
API Client v1.0.0 initialized
Token expires in 3600 seconds
GET https://api.example.com/users with token: token_1756882290

The hierarchical organization makes the code self-documenting while maintaining clear separation between authentication, endpoints, and configuration concerns. This pattern scales well for complex applications.

Like classes, modules support method visibility to control access to their functionality.

Method visibility in modules

Modules respect the same visibility rules as classes, supporting private, protected, and public methods:

module_visibility.rb
module Database
  def connect
    establish_connection
    puts "Connected to database"
  end

  def query(sql)
    validate_connection
    puts "Executing: #{sql}"
  end

  private

  def establish_connection
    puts "Establishing connection..."
  end

  def validate_connection
    puts "Validating connection..."
  end
end

class UserRepository
  include Database

  def find_user(id)
    query("SELECT * FROM users WHERE id = #{id}")
  end

  def test_private_access
    # This works - private methods from modules are accessible
    establish_connection
  end
end

repo = UserRepository.new
repo.connect
repo.find_user(123)
repo.test_private_access

# This would raise an error:
# repo.establish_connection  # private method

Private methods in modules become private in the including class, maintaining encapsulation while enabling internal functionality. This allows modules to provide clean public interfaces while hiding implementation details.

 
ruby module_visibility.rb
Output
Establishing connection...
Connected to database
Validating connection...
Executing: SELECT * FROM users WHERE id = 123
Establishing connection...

The module's private methods work exactly as expected - accessible within the class but hidden from external callers. This maintains the principle of encapsulation while enabling code reuse.

Final thoughts

Ruby modules provide powerful mechanisms for code organization and reuse. They solve real problems in software design by enabling namespacing, mixins, and modular architecture. The key concepts to remember are:

  • Modules cannot be instantiated, but provide containers for shared functionality
  • Use include for instance methods, extend for class methods, and prepend for method wrapping
  • Modules create excellent namespaces to avoid naming conflicts
  • The self.included hook allows modules to add both class and instance methods
  • Follow naming conventions and single responsibility principles
Got an article suggestion? Let us know
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.