Overview
Module methods in Ruby encompass several distinct patterns for organizing and accessing functionality at the module level. Ruby modules can define methods directly on themselves, provide methods to classes through inclusion and extension, and expose functionality through various method visibility patterns.
Ruby implements module methods through three primary mechanisms. Module-level methods use self
or module_function
to create methods callable directly on the module. Modules can also define instance methods that become available to classes through include
, or class methods through extend
. Additionally, modules support nested method definitions and constant access patterns.
module Calculator
def self.add(a, b)
a + b
end
def multiply(a, b)
a * b
end
module_function :multiply
end
Calculator.add(5, 3) # => 8
Calculator.multiply(4, 2) # => 8
The module method system provides namespace organization, code reuse, and mixin functionality. Ruby's method lookup chain resolves module methods based on inclusion order and method visibility. Modules cannot be instantiated directly, making their method access patterns distinct from class-based approaches.
module Authentication
def self.hash_password(password)
"hashed_#{password}"
end
def authenticate(user, password)
user.password_hash == Authentication.hash_password(password)
end
end
class User
include Authentication
attr_accessor :password_hash
end
user = User.new
user.authenticate(user, "secret") # Instance method from module
Authentication.hash_password("secret") # Module-level method
Module methods support both functional and object-oriented programming patterns. They enable utility functions, namespace organization, and controlled method exposure across Ruby applications.
Basic Usage
Module methods begin with defining methods directly on the module using self
keyword or module_function
directive. The self
approach creates methods accessible only on the module itself, while module_function
creates both private instance methods and public module methods.
module FileUtils
def self.read_lines(filename)
File.readlines(filename).map(&:chomp)
end
def copy_file(source, destination)
File.write(destination, File.read(source))
end
module_function :copy_file
end
# Direct module method call
lines = FileUtils.read_lines("config.txt")
# Module function - can be called on module or included in classes
FileUtils.copy_file("source.txt", "dest.txt")
class FileManager
include FileUtils
def backup_config
copy_file("config.txt", "config.backup") # Available as instance method
end
end
Method visibility in modules follows Ruby's standard private, protected, and public patterns. Private module methods require explicit receiver specification, while public methods can be called directly. The module_function
directive automatically makes methods private when used as instance methods.
module Logger
def self.info(message)
puts "[INFO] #{message}"
end
def debug(message)
puts "[DEBUG] #{timestamp} #{message}" if debug_enabled?
end
def timestamp
Time.now.strftime("%Y-%m-%d %H:%M:%S")
end
module_function :debug
private :timestamp
end
Logger.info("Application started") # Public module method
Logger.debug("Processing request") # Module function
class Service
include Logger
def process
debug("Service processing") # Available as instance method
end
end
Modules support constant definition and access, providing namespace organization for both values and nested modules. Constants defined in modules can be accessed using the scope resolution operator or through module inclusion.
module Config
DATABASE_URL = "postgresql://localhost/myapp"
MAX_CONNECTIONS = 20
module Cache
TTL = 3600
ENABLED = true
end
def self.database_config
{ url: DATABASE_URL, max_connections: MAX_CONNECTIONS }
end
end
puts Config::DATABASE_URL # => "postgresql://localhost/myapp"
puts Config::Cache::TTL # => 3600
config = Config.database_config # Method access to constants
Module inclusion and extension provide different method availability patterns. Including a module makes its instance methods available to the including class, while extending makes them available as class methods. This distinction enables flexible code organization patterns.
module Comparable
def between?(min, max)
self >= min && self <= max
end
def clamp(min, max)
return min if self < min
return max if self > max
self
end
end
class Temperature
include Comparable
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def <=>(other)
celsius <=> other.celsius
end
end
temp = Temperature.new(25)
puts temp.between?(Temperature.new(20), Temperature.new(30)) # => true
puts temp.clamp(Temperature.new(0), Temperature.new(40)) # Temperature object
Advanced Usage
Module methods support complex metaprogramming patterns through dynamic method definition and method aliasing. The define_method
approach enables runtime method creation based on configuration or external data, while method aliasing provides backward compatibility and method decoration patterns.
module DynamicFinders
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def create_finder(attribute)
define_method("find_by_#{attribute}") do |value|
instance_variables.map { |var| instance_variable_get(var) }
.find { |obj| obj.send(attribute) == value }
end
define_method("find_all_by_#{attribute}") do |value|
instance_variables.map { |var| instance_variable_get(var) }
.select { |obj| obj.send(attribute) == value }
end
end
end
end
class UserRepository
include DynamicFinders
create_finder(:email)
create_finder(:status)
def initialize
@users = []
end
def add_user(user)
@users << user
instance_variable_set("@user_#{@users.length}", user)
end
end
Module composition enables building complex functionality from smaller, focused modules. Ruby's method resolution order determines which methods take precedence when multiple modules define the same method name. This enables decorator and strategy patterns at the module level.
module Cacheable
def cached_result(key)
@cache ||= {}
@cache[key] ||= yield
end
def clear_cache
@cache = {}
end
end
module Loggable
def with_logging(operation)
puts "Starting #{operation}"
result = yield
puts "Completed #{operation}"
result
end
end
module Measurable
def measure_time
start_time = Time.now
result = yield
end_time = Time.now
puts "Execution time: #{end_time - start_time} seconds"
result
end
end
class DataProcessor
include Cacheable
include Loggable
include Measurable
def process_data(input)
cached_result("process_#{input.hash}") do
with_logging("data processing") do
measure_time do
# Expensive data processing operation
sleep(1)
input.upcase
end
end
end
end
end
Module methods can implement the Template Method pattern by defining algorithmic structure while allowing subclasses or including classes to provide specific implementations. This pattern combines module inclusion with method overriding for customizable behavior.
module Serializable
def self.included(base)
base.extend(ClassMethods)
end
def serialize
{
class: self.class.name,
data: serialize_attributes,
metadata: generate_metadata
}.to_json
end
def serialize_attributes
instance_variables.each_with_object({}) do |var, hash|
attr_name = var.to_s.gsub('@', '')
hash[attr_name] = instance_variable_get(var)
end
end
def generate_metadata
{
serialized_at: Time.now.iso8601,
version: self.class.serialization_version
}
end
module ClassMethods
def serialization_version
"1.0"
end
def deserialize(json_string)
data = JSON.parse(json_string)
instance = new
data['data'].each { |key, value| instance.send("#{key}=", value) }
instance
end
end
end
class Product
include Serializable
attr_accessor :name, :price, :description
def self.serialization_version
"2.1"
end
def serialize_attributes
# Custom serialization excluding sensitive data
{
name: @name,
price: @price,
description: @description&.truncate(100)
}
end
end
Module methods support callback and hook mechanisms through method interception and the method_added
, included
, and extended
hooks. These patterns enable aspect-oriented programming and automatic configuration based on method definitions or module usage.
module Validatable
def self.included(base)
base.extend(ClassMethods)
base.class_eval { @validations = [] }
end
def valid?
self.class.validations.all? do |validation|
case validation[:type]
when :presence
!send(validation[:attribute]).nil? && !send(validation[:attribute]).to_s.empty?
when :length
value = send(validation[:attribute]).to_s
value.length >= validation[:min] && value.length <= validation[:max]
when :format
send(validation[:attribute]).to_s.match?(validation[:pattern])
end
end
end
def errors
self.class.validations.filter_map do |validation|
attribute = validation[:attribute]
case validation[:type]
when :presence
"#{attribute} cannot be blank" if send(attribute).nil? || send(attribute).to_s.empty?
when :length
value = send(attribute).to_s
if value.length < validation[:min]
"#{attribute} must be at least #{validation[:min]} characters"
elsif value.length > validation[:max]
"#{attribute} must be at most #{validation[:max]} characters"
end
when :format
"#{attribute} format is invalid" unless send(attribute).to_s.match?(validation[:pattern])
end
end
end
module ClassMethods
attr_reader :validations
def validates_presence_of(attribute)
@validations << { type: :presence, attribute: attribute }
end
def validates_length_of(attribute, min:, max:)
@validations << { type: :length, attribute: attribute, min: min, max: max }
end
def validates_format_of(attribute, with:)
@validations << { type: :format, attribute: attribute, pattern: with }
end
end
end
Common Pitfalls
Module method visibility can create unexpected behavior when methods defined as module functions interact with private methods. Private methods in modules remain private when the module is included, but module functions become public when called on the module directly.
module Calculator
def add(a, b)
log_operation("add", a, b)
a + b
end
def log_operation(op, *args)
puts "Performing #{op} with #{args.join(', ')}"
end
module_function :add
private :log_operation
end
# This works - log_operation is accessible within module context
Calculator.add(5, 3) # => "Performing add with 5, 3" then 8
class MathService
include Calculator
def calculate
add(2, 3) # This fails! log_operation is private in instance context
end
end
# Fix: Make log_operation a module function or use module-level logging
module BetterCalculator
def add(a, b)
BetterCalculator.log_operation("add", a, b)
a + b
end
def self.log_operation(op, *args)
puts "Performing #{op} with #{args.join(', ')}"
end
module_function :add
end
Constant lookup in modules follows Ruby's lexical scoping rules, but can produce unexpected results when constants are defined in including classes versus the module itself. Constants are resolved at the point of definition, not at the point of use.
module Configurable
DEFAULT_TIMEOUT = 30
def timeout_value
DEFAULT_TIMEOUT # Always resolves to module's constant
end
def current_timeout
self.class::DEFAULT_TIMEOUT # Attempts to resolve from including class
end
end
class WebService
include Configurable
DEFAULT_TIMEOUT = 60 # Different value
end
class ApiClient
include Configurable
# No DEFAULT_TIMEOUT defined
end
web = WebService.new
api = ApiClient.new
web.timeout_value # => 30 (module constant)
web.current_timeout # => 60 (class constant)
api.timeout_value # => 30 (module constant)
api.current_timeout # => NameError: uninitialized constant ApiClient::DEFAULT_TIMEOUT
# Better approach: Use class-level configuration
module BetterConfigurable
def self.included(base)
base.extend(ClassMethods)
end
def timeout_value
self.class.default_timeout
end
module ClassMethods
def default_timeout
defined?(self::DEFAULT_TIMEOUT) ? self::DEFAULT_TIMEOUT : 30
end
end
end
Method resolution order complications arise when multiple modules define the same method and are included in different orders. The last included module takes precedence, but this can create subtle bugs when module order changes.
module DatabaseLogging
def save
puts "Logging to database"
super
end
end
module FileLogging
def save
puts "Logging to file"
super
end
end
module Caching
def save
puts "Updating cache"
super
end
end
class Document
include DatabaseLogging
include FileLogging
include Caching
def save
puts "Saving document"
end
end
# Method resolution order: Document -> Caching -> FileLogging -> DatabaseLogging
doc = Document.new
doc.save
# Output:
# Updating cache
# Logging to file
# Logging to database
# Saving document
# Changing include order changes behavior
class ReorderedDocument
include Caching
include DatabaseLogging
include FileLogging
def save
puts "Saving document"
end
end
# Now FileLogging comes last and executes first
redoc = ReorderedDocument.new
redoc.save
# Output:
# Logging to file
# Logging to database
# Updating cache
# Saving document
Module extension versus inclusion creates different method availability that can confuse developers. Extended methods become class methods, while included methods become instance methods. Mixing these patterns in the same module can create inconsistent interfaces.
module Utilities
def format_currency(amount)
"$%.2f" % amount
end
def self.format_currency(amount)
"$%.2f" % amount
end
end
class Product
extend Utilities # Methods become class methods
end
class Order
include Utilities # Methods become instance methods
end
# Different access patterns for the same functionality
Product.format_currency(29.99) # Works - class method
Order.new.format_currency(29.99) # Works - instance method
# These don't work:
# Product.new.format_currency(29.99) # NoMethodError
# Order.format_currency(29.99) # NoMethodError
# Better approach: Design for both patterns
module BetterUtilities
def format_currency(amount)
BetterUtilities.format_currency(amount)
end
def self.format_currency(amount)
"$%.2f" % amount
end
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def format_currency(amount)
BetterUtilities.format_currency(amount)
end
end
end
Reference
Module Method Definition Patterns
Pattern | Syntax | Access | Description |
---|---|---|---|
self.method |
def self.method_name |
Module.method_name |
Module-level method, callable only on module |
module_function |
module_function :method_name |
Module.method_name or as instance method |
Creates both module and private instance method |
Class method via extend | extend ModuleName |
Class.method_name |
Module methods become class methods |
Instance method via include | include ModuleName |
instance.method_name |
Module methods become instance methods |
Method Visibility in Modules
Visibility | Module Call | Instance Call | Inheritance |
---|---|---|---|
public |
Available | Available | Inherited as public |
protected |
Available | Available to same class | Inherited as protected |
private |
Available | Not available | Inherited as private |
module_function |
Public | Private | Inherited as private |
Module Hooks and Callbacks
Hook | Triggered When | Common Use Cases |
---|---|---|
included(base) |
Module is included | Add class methods, initialize class variables |
extended(base) |
Module is extended | Configure extended class, add metadata |
method_added(method_name) |
Method is defined | Automatic registration, validation setup |
prepended(base) |
Module is prepended | Override methods while preserving original |
Constant Resolution Patterns
# Constant access patterns
module Constants
VALUE = "module_value"
def self.get_value
VALUE # Module's own constant
end
def get_class_value
self.class::VALUE # Including class constant
end
def get_qualified_value
Constants::VALUE # Fully qualified constant
end
end
Method Resolution Order (MRO)
Ruby resolves methods in the following order:
- Singleton methods on the object
- Methods in prepended modules (reverse order of prepending)
- Methods in the object's class
- Methods in included modules (reverse order of inclusion)
- Methods in parent classes and their modules
method_missing
if defined
Common Module Patterns
Pattern | Implementation | Use Case |
---|---|---|
Namespace | module Name; class X; end; end |
Group related classes |
Mixin | include ModuleName |
Share behavior across classes |
Utility | module Util; def self.method; end; end |
Stateless helper functions |
Configuration | module Config; SETTING = value; end |
Application settings |
Template | Abstract methods with raise NotImplementedError |
Define algorithms with customizable steps |
Error Types and Module Methods
Error | Cause | Solution |
---|---|---|
NoMethodError |
Calling undefined module method | Check method definition and visibility |
NameError |
Undefined constant reference | Verify constant exists in expected scope |
ArgumentError |
Wrong number of method arguments | Check method signature |
TypeError |
Module used where class expected | Use appropriate include/extend pattern |
LoadError |
Module file not found | Check require/load statements |
Module Method Performance Characteristics
Operation | Complexity | Notes |
---|---|---|
Module method call | O(1) | Direct method lookup |
Constant access | O(1) | Cached after first access |
Method resolution | O(n) | Where n is ancestry chain length |
Module inclusion | O(1) | Updates ancestor chain |
Dynamic method definition | O(1) | Runtime method creation |