Overview
Ruby provides two primary mechanisms for incorporating module functionality: include
and extend
. Both add module methods to receiving objects, but they differ fundamentally in how and where those methods become available.
When a class includes a module, Ruby inserts the module into the class's ancestor chain above the class itself. This makes the module's instance methods available as instance methods of the class. Every object created from that class can call these methods.
module Greetings
def hello
"Hello from #{self.class}"
end
end
class Person
include Greetings
end
person = Person.new
person.hello # => "Hello from Person"
The extend
mechanism works differently. When an object extends a module, Ruby adds the module's instance methods directly to that specific object as singleton methods. These methods become available only on the extended object, not on other instances of the same class.
module Greetings
def hello
"Hello from #{self.class}"
end
end
class Person
end
person = Person.new
person.extend(Greetings)
person.hello # => "Hello from Person"
another_person = Person.new
another_person.hello # NoMethodError
When extend
operates at the class level, it adds the module's methods as class methods rather than instance methods. This happens because classes in Ruby are objects, and extending a class object adds singleton methods to that class object.
class Person
extend Greetings
end
Person.hello # => "Hello from Class"
Ruby's method lookup follows a specific path through the ancestor chain. For included modules, Ruby searches the module before searching the class that included it. For extended modules, Ruby searches the singleton class first, where the extended methods reside.
The inheritance hierarchy changes differently with each approach. Including a module modifies the class's ancestor chain, affecting all instances. Extending modifies only the specific object's singleton class, leaving other instances unchanged.
Both mechanisms respect Ruby's method visibility rules. Private and protected methods in modules maintain their visibility when included or extended, though the context in which they operate changes based on the inclusion method.
Basic Usage
Include modules when multiple instances of a class need the same set of methods. This pattern works best for shared behavior that logically belongs to all instances of a class.
module Trackable
def track_event(event)
puts "Tracking: #{event} for #{self.class}"
end
def last_tracked
@last_event
end
end
class User
include Trackable
def initialize(name)
@name = name
end
end
class Product
include Trackable
def initialize(sku)
@sku = sku
end
end
user = User.new("Alice")
user.track_event("login") # Tracking: login for User
product = Product.new("ABC123")
product.track_event("view") # Tracking: view for Product
The included module methods access instance variables and other instance methods of the including class. The module code executes in the context of the instance that calls it.
module Measurable
def measure
@measurements ||= []
end
def add_measurement(value)
measure << value
calculate_average
end
private
def calculate_average
@average = measure.sum / measure.size.to_f
end
end
class Temperature
include Measurable
def current_average
@average || 0
end
end
temp = Temperature.new
temp.add_measurement(72.5)
temp.add_measurement(74.1)
temp.current_average # => 73.3
Use extend
for adding functionality to specific objects without affecting their class or other instances. This works well for object-specific behavior or one-off customizations.
module AdminCapabilities
def delete_user(user_id)
"Deleting user #{user_id}"
end
def view_logs
"Accessing system logs"
end
end
regular_user = User.new("Bob")
admin_user = User.new("Carol")
admin_user.extend(AdminCapabilities)
admin_user.delete_user(123) # => "Deleting user 123"
regular_user.delete_user(123) # NoMethodError
Class-level extension adds methods as class methods, making them available without instantiating objects. This pattern suits factory methods, configuration, and utility functions.
module Buildable
def build_from_hash(hash)
instance = new
hash.each { |key, value| instance.instance_variable_set("@#{key}", value) }
instance
end
def default_attributes
{ status: "active", created_at: Time.now }
end
end
class Document
extend Buildable
attr_reader :title, :status, :created_at
def initialize
self.class.default_attributes.each do |key, value|
instance_variable_set("@#{key}", value)
end
end
end
doc = Document.build_from_hash(title: "Ruby Guide", status: "draft")
doc.title # => "Ruby Guide"
doc.status # => "draft"
Advanced Usage
Multiple modules can be included or extended simultaneously, with method lookup following the reverse order of inclusion. The last included module appears first in the ancestor chain.
module A
def test; "A"; end
end
module B
def test; "B"; end
end
class Example
include A
include B
end
Example.new.test # => "B"
Example.ancestors # => [Example, B, A, Object, Kernel, BasicObject]
Modules can include other modules, creating complex hierarchies. When a class includes a module that includes other modules, all modules enter the ancestor chain in the order they were processed.
module Database
def save
"Saving to database"
end
end
module Validations
include Database
def validate
return false unless valid?
save
end
private
def valid?
true
end
end
module Timestamps
def touch
@updated_at = Time.now
end
end
class Record
include Validations
include Timestamps
end
Record.ancestors
# => [Record, Timestamps, Validations, Database, Object, Kernel, BasicObject]
Dynamic extension allows runtime modification of objects based on conditions or user input. This technique enables flexible object behavior without subclassing.
module JsonSerializer
def to_json
instance_variables.each_with_object({}) do |var, hash|
key = var.to_s.delete("@")
hash[key] = instance_variable_get(var)
end.to_json
end
end
module XmlSerializer
def to_xml
"<record>#{instance_variables.map { |v|
key = v.to_s.delete("@")
value = instance_variable_get(v)
"<#{key}>#{value}</#{key}>"
}.join}</record>"
end
end
class DataRecord
def initialize(format = :json)
@data = "sample"
case format
when :json then extend JsonSerializer
when :xml then extend XmlSerializer
end
end
end
json_record = DataRecord.new(:json)
json_record.to_json # => "{\"data\":\"sample\"}"
xml_record = DataRecord.new(:xml)
xml_record.to_xml # => "<record><data>sample</data></record>"
Module prepending with prepend
places modules before the class in the ancestor chain, allowing modules to override class methods while maintaining access to the original implementation through super
.
module Loggable
def process_data(data)
puts "Starting processing: #{data}"
result = super(data)
puts "Finished processing: #{result}"
result
end
end
class DataProcessor
prepend Loggable
def process_data(data)
data.upcase
end
end
processor = DataProcessor.new
processor.process_data("hello")
# Starting processing: hello
# Finished processing: HELLO
# => "HELLO"
Extend can modify the behavior of individual objects within collection processing, applying different capabilities based on object properties or external conditions.
module Prioritizable
def priority_score
(@priority || 1) * 10
end
end
module Urgent
def priority_score
super + 50
end
end
tasks = [
{ name: "Deploy", priority: 3 },
{ name: "Review", priority: 1 },
{ name: "Hotfix", priority: 2, urgent: true }
]
processed_tasks = tasks.map do |task_data|
task = Struct.new(:name, :priority).new(task_data[:name], task_data[:priority])
task.extend(Prioritizable)
task.extend(Urgent) if task_data[:urgent]
task
end
processed_tasks.each { |task| puts "#{task.name}: #{task.priority_score}" }
# Deploy: 30
# Review: 10
# Hotfix: 70
Common Pitfalls
The most frequent confusion involves misunderstanding where extended methods become available. Extending an instance adds methods only to that specific object, not to the class or other instances.
module Helpers
def utility_method
"Available only where extended"
end
end
class Worker
end
# Wrong expectation: this won't affect other Worker instances
worker1 = Worker.new
worker1.extend(Helpers)
worker1.utility_method # => "Available only where extended"
worker2 = Worker.new
worker2.utility_method # NoMethodError: undefined method `utility_method'
Class extension versus instance extension creates different method availability. Many developers expect Class.extend(Module)
to make methods available on instances, but it makes them available as class methods.
module Calculatable
def add(a, b)
a + b
end
end
# This makes add available as a class method, not instance method
class MathHelper
extend Calculatable
end
MathHelper.add(2, 3) # => 5
MathHelper.new.add(2, 3) # NoMethodError
# To make add available on instances, use include
class MathHelper2
include Calculatable
end
MathHelper2.new.add(2, 3) # => 5
MathHelper2.add(2, 3) # NoMethodError (no class method)
Module method lookup through super
behaves differently with include, extend, and prepend. Understanding the ancestor chain helps predict which method super
calls.
module A
def test
puts "A"
super if defined?(super)
end
end
module B
def test
puts "B"
super if defined?(super)
end
end
class Base
def test
puts "Base"
end
end
class Example < Base
include A
include B
end
# B → A → Base
Example.new.test
# B
# A
# Base
# But with extend on an instance:
example = Base.new
example.extend(A)
example.extend(B)
example.test
# B
# A
# Base (calls the original instance method)
Instance variable access differs between included and extended modules. Extended modules on classes cannot access instance variables of instances created from those classes.
module InstanceVarAccess
def get_name
@name
end
def set_name(name)
@name = name
end
end
class Person
def initialize(name)
@name = name
end
end
# Include: works with instance variables
class Person1 < Person
include InstanceVarAccess
end
person1 = Person1.new("Alice")
person1.get_name # => "Alice"
# Extend on class: creates class-level instance variables
class Person2 < Person
extend InstanceVarAccess
end
Person2.set_name("Bob")
Person2.get_name # => "Bob" (class instance variable)
person2 = Person2.new("Charlie")
person2.get_name # NoMethodError: undefined method `get_name'
Method visibility (private, protected) can produce unexpected results when modules are included versus extended. Private methods in extended modules become private singleton methods, which behave differently from regular private methods.
module SecretMethods
private
def secret
"Secret information"
end
public
def reveal_secret
secret
end
end
class Container
include SecretMethods
end
container = Container.new
container.reveal_secret # => "Secret information"
container.secret # NoMethodError (private method)
# With extend:
other_container = Container.new
other_container.extend(SecretMethods)
other_container.reveal_secret # => "Secret information"
other_container.secret # NoMethodError (private singleton method)
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
include |
module |
self |
Adds module methods as instance methods |
extend |
module |
obj |
Adds module methods as singleton methods |
prepend |
module |
self |
Adds module before class in ancestor chain |
included_modules |
none | Array |
Returns array of included modules |
ancestors |
none | Array |
Returns ancestor chain including modules |
Module Callbacks
Callback | Trigger | Parameters | Usage |
---|---|---|---|
included |
Module included in class/module | base |
Hook for include operations |
extended |
Module extends object | object |
Hook for extend operations |
prepended |
Module prepended to class/module | base |
Hook for prepend operations |
Method Lookup Order
Operation | Lookup Path | Example |
---|---|---|
Include | Class → Modules (reverse order) → Superclass | [Class, ModuleB, ModuleA, Superclass] |
Extend | Singleton class → Class → Modules → Superclass | [#<Class:obj>, Class, Modules, Superclass] |
Prepend | Modules (reverse order) → Class → Superclass | [ModuleB, ModuleA, Class, Superclass] |
Visibility Rules
Method Type | Include Behavior | Extend Behavior |
---|---|---|
public |
Public instance method | Public singleton method |
private |
Private instance method | Private singleton method |
protected |
Protected instance method | Protected singleton method |
Common Patterns
Pattern | Implementation | Use Case |
---|---|---|
Mixin | include Module |
Shared instance behavior |
Class methods | extend Module |
Utility functions, factories |
Decorator | object.extend(Module) |
Single object enhancement |
Wrapper | prepend Module |
Method interception |
Decision Matrix
Goal | Use | Example |
---|---|---|
All instances need methods | include |
Validation, persistence |
Class needs utility methods | extend |
Factories, builders |
One object needs methods | instance.extend |
Admin capabilities |
Override with super access | prepend |
Logging, monitoring |
Chain multiple behaviors | Multiple includes | Authentication + logging |
Introspection Methods
Method | Returns | Description |
---|---|---|
obj.singleton_methods |
Array<Symbol> |
Methods added via extend |
Class.included_modules |
Array<Module> |
Modules included in class |
obj.methods - Class.instance_methods |
Array<Symbol> |
Object-specific methods |
Module.nesting |
Array<Module> |
Current module nesting context |