Skip to content

Ruby Modules

Modules in Ruby serve two primary purposes:

  • Namespacing: They provide a way to group related methods, classes, and constants, preventing name collisions between different parts of your codebase or with third-party libraries.
  • Mixins: They allow you to share common functionality (instance methods) among multiple classes without using traditional inheritance, offering a form of multiple inheritance.

A module is defined using the module keyword, followed by the module name (conventionally CamelCase), and ends with end.

module MyLogger
PI = 3.14159 # A constant within the module
# A module method (called on the module itself)
def self.log(message)
puts "[LOG] #{Time.now}: #{message}"
end
# An instance method (intended for mixing into classes)
def print_info(text)
"INFO: #{text}"
end
end

Accessing Module Constants and Methods:

Module constants are accessed using the :: (scope resolution) operator: MyLogger::PI.

Module methods (defined with self. or ModuleName.) are called directly on the module: MyLogger.log("Application started").

Ruby require and require_relative Statements:

Section titled “Ruby require and require_relative Statements:”

To use a module defined in a separate file, you need to load that file. Ruby provides two main ways to do this:

  • require 'filename': Loads a file. Ruby searches for this file in directories listed in the $LOAD_PATH (an array of paths). It’s typically used for loading installed gems or standard library features. You usually omit the .rb extension.
  • require_relative 'filename': Loads a file relative to the path of the file containing the require_relative statement. This is preferred for loading your own project files. You usually include the .rb extension or omit it if it’s clear.

Imagine two different libraries defining a Utility module.

payments_utility.rb
module Payments
module Utility
def self.format_currency(amount)
"$%.2f" % amount
end
end
end
# File: reporting_utility.rb
module Reporting
module Utility
def self.generate_report_name(base_name)
"#{base_name}_#{Time.now.strftime('%Y%m%d')}.pdf"
end
end
end
# File: main_app.rb
require_relative 'payments_utility'
require_relative 'reporting_utility'
puts Payments::Utility.format_currency(123.45) # Output: $123.45
puts Reporting::Utility.generate_report_name("sales") # Output: sales_20230315.pdf (date will vary)

This demonstrates how modules prevent name clashes. Payments::Utility is distinct from Reporting::Utility.

The include keyword is used to mix a module’s instance methods and constants into a class. When a class includes a module, the module’s instance methods become instance methods of the class.

Example of include for Mixins:

printable.rb
module Printable
# This instance method will be mixed into any class that includes Printable
def pretty_print
"<#{self.class} - #{self.object_id}> Attributes: #{instance_variables.map { |var| "#{var}=#{instance_variable_get(var).inspect}" }.join(', ')}"
end
end
# File: book.rb
require_relative 'printable'
class Book
include Printable # Mixes in the pretty_print method from Printable module
attr_accessor :title, :author
def initialize(title, author)
@title = title
@author = author
end
end
class Magazine
include Printable
attr_accessor :name, :issue_date
def initialize(name, issue_date)
@name = name
@issue_date = issue_date
end
end
# File: app.rb
require_relative 'book'
require_relative 'magazine'
my_book = Book.new("The Great Gatsby", "F. Scott Fitzgerald")
my_magazine = Magazine.new("Tech Weekly", "2023-10-26")
puts my_book.pretty_print
# Example Output: <Book - 701...> Attributes: @title="The Great Gatsby", @author="F. Scott Fitzgerald"
puts my_magazine.pretty_print
# Example Output: <Magazine - 702...> Attributes: @name="Tech Weekly", @issue_date="2023-10-26"

In this example, both Book and Magazine classes gain the pretty_print functionality without inheriting from a common superclass for this specific behavior. This is the power of mixins.

Constants from an included module are also accessible directly within the class (e.g., if Printable had a constant VERSION, Book could access it as VERSION).

Ruby extend Statement: Adding Class Methods

Section titled “Ruby extend Statement: Adding Class Methods”

While include adds a module’s methods as instance methods, extend adds them as class methods to the class.

class_utils.rb
module ClassUtils
def find(id)
# Hypothetical find logic for a class method
puts "#{self.name} finding record with id: #{id}"
# new(id: id) # for example
end
end
# File: user.rb
require_relative 'class_utils'
class User
extend ClassUtils # Adds ClassUtils methods as class methods to User
attr_accessor :id, :name
def initialize(id: nil, name: nil)
@id = id
@name = name
end
end
# File: app.rb
require_relative 'user'
User.find(101) # Calls the 'find' method as a class method on User
# Output: User finding record with id: 101

Introduced in Ruby 2.0, prepend is similar to include but inserts the module in the inheritance chain before the class itself. This means methods in the prepended module can override methods in the class and can use super to call the class’s original method. It’s a more advanced feature often used for method decoration or modifying behavior in a more intrusive way than include.

module LoggingPrepender
def save
puts "[PREPEND LOG] Before saving #{self.class.name}..."
result = super # Calls the original 'save' method of the class
puts "[PREPEND LOG] After saving #{self.class.name}... Result: #{result}"
result
end
end
class Document
prepend LoggingPrepender
def save
puts "Document saving..."
true # Simulate successful save
end
end
doc = Document.new
doc.save
# Output:
# [PREPEND LOG] Before saving Document...
# Document saving...
# [PREPEND LOG] After saving Document... Result: true

Practical Applications:

Modules are extensively used in Ruby. The entire Ruby standard library is organized into modules (e.g., Math, File, Enumerable). Rails framework components like ActiveRecord::Base or ActionController::Base include numerous modules to provide their rich feature sets. You’ll use modules to organize your own code, share utility functions, and implement cross-cutting concerns like logging or serialization.