Overview
The include
method in Ruby mixes modules into classes, making the module's instance methods available to instances of the class. Ruby places the included module directly above the class in the method lookup chain, creating a linear inheritance hierarchy that preserves method resolution order.
When a class includes a module, Ruby creates an anonymous class that wraps the module and inserts it into the inheritance chain. This mechanism differs from composition or delegation - the included methods become true instance methods of the class, accessible through normal method calls without any special syntax.
module Greetings
def hello
"Hello from #{self.class}"
end
end
class Person
include Greetings
end
person = Person.new
person.hello # => "Hello from Person"
Ruby processes multiple includes in reverse order of inclusion. The last module included appears first in the method lookup chain, allowing later includes to override methods from earlier includes.
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]
The include
method returns the class that performed the inclusion, enabling method chaining. Ruby calls the included
callback hook on the module when inclusion occurs, providing modules the opportunity to modify the including class through metaprogramming.
Basic Usage
Classes include modules by calling the include
method with one or more module names as arguments. The syntax accepts multiple modules in a single call, processing them from left to right.
module Printable
def print_info
puts "#{self.class}: #{self}"
end
end
module Comparable
def <=>(other)
self.id <=> other.id
end
end
class Document
include Printable, Comparable
attr_reader :id, :title
def initialize(id, title)
@id = id
@title = title
end
def to_s
@title
end
end
doc = Document.new(1, "Ruby Guide")
doc.print_info # => "Document: Ruby Guide"
Ruby raises a TypeError
if the argument passed to include
is not a module. Classes cannot be included, only extended or inherited from using different mechanisms.
class NotAModule
end
class Example
include NotAModule # => TypeError: wrong argument type Class (expected Module)
end
Modules can include other modules, creating nested inclusion chains. Ruby flattens these chains when the outer module gets included into a class, maintaining the proper method lookup order.
module Inner
def inner_method
"inner"
end
end
module Outer
include Inner
def outer_method
"outer"
end
end
class Container
include Outer
end
Container.new.inner_method # => "inner"
Container.ancestors # => [Container, Outer, Inner, Object, Kernel, BasicObject]
The included_modules
method returns an array of modules directly included by a class, excluding modules included by superclasses or modules that include other modules.
class Base
include Enumerable
end
class Child < Base
include Comparable
end
Child.included_modules # => [Comparable]
Base.included_modules # => [Enumerable]
Advanced Usage
Ruby's included
callback hook enables modules to modify classes during inclusion. The callback receives the including class as an argument, allowing modules to define class methods, create accessors, or perform other metaprogramming operations.
module Trackable
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
attr_accessor :tracking_id
@tracked_instances = []
end
end
module ClassMethods
def tracked_instances
@tracked_instances ||= []
end
def track_instance(instance)
tracked_instances << instance
end
end
def initialize(*args)
super
self.class.track_instance(self)
self.tracking_id = SecureRandom.uuid
end
end
class Product
include Trackable
def initialize(name)
@name = name
super()
end
end
p1 = Product.new("Laptop")
p2 = Product.new("Phone")
Product.tracked_instances.length # => 2
Modules can conditionally define methods based on the including class's characteristics. This pattern creates adaptive interfaces that respond to the context of inclusion.
module Cacheable
def self.included(base)
if base.respond_to?(:cache_store)
base.send(:include, WithCacheStore)
else
base.send(:include, WithoutCacheStore)
end
end
module WithCacheStore
def cached_result(key)
self.class.cache_store.fetch(key) { yield }
end
end
module WithoutCacheStore
def cached_result(key)
yield
end
end
end
class FastService
def self.cache_store
@cache_store ||= {}
end
include Cacheable
end
class SimpleService
include Cacheable
end
Method delegation through include
can create complex forwarding patterns. Modules can intercept method calls and forward them to contained objects while maintaining the appearance of native method calls.
module Delegatable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def delegate(*methods, to:)
methods.each do |method|
define_method(method) do |*args, **kwargs, &block|
target = instance_variable_get("@#{to}")
target.send(method, *args, **kwargs, &block)
end
end
end
end
end
class Wrapper
include Delegatable
delegate :length, :empty?, :[], to: :items
def initialize
@items = []
end
def add(item)
@items << item
end
end
wrapper = Wrapper.new
wrapper.add("test")
wrapper.length # => 1
wrapper.empty? # => false
wrapper[0] # => "test"
Modules can create domain-specific languages through method inclusion. The included methods can modify the including class's behavior in sophisticated ways, creating fluent interfaces or configuration DSLs.
module Configurable
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
@configuration = {}
end
end
module ClassMethods
def configure(&block)
ConfigurationBuilder.new(@configuration).instance_eval(&block)
end
def config
@configuration
end
end
class ConfigurationBuilder
def initialize(config)
@config = config
end
def set(key, value)
@config[key] = value
end
def timeout(seconds)
@config[:timeout] = seconds
end
def retries(count)
@config[:retries] = count
end
end
def configuration
self.class.config
end
end
class ApiClient
include Configurable
configure do
timeout 30
retries 3
set :base_url, "https://api.example.com"
end
end
ApiClient.new.configuration[:timeout] # => 30
Common Pitfalls
Module inclusion order significantly affects method resolution, but the effect runs counter to many developers' intuitions. Ruby searches the method lookup chain from bottom to top, meaning the last included module takes precedence over earlier includes.
module Authentication
def current_user
"authenticated_user"
end
end
module Authorization
def current_user
"authorized_user"
end
end
# Wrong assumption: Authentication will override Authorization
class Controller
include Authorization
include Authentication # This takes precedence
end
Controller.new.current_user # => "authenticated_user"
# Correct order for Authorization to win
class CorrectController
include Authentication
include Authorization # This takes precedence
end
CorrectController.new.current_user # => "authorized_user"
The super
keyword in included methods calls the next method in the lookup chain, not necessarily the method in the including class. This behavior creates unexpected results when multiple modules define the same method and use super
.
module A
def process
puts "A processing"
super if defined?(super)
end
end
module B
def process
puts "B processing"
super if defined?(super)
end
end
class Processor
include A
include B
def process
puts "Processor processing"
end
end
# Lookup chain: Processor -> B -> A -> Object
Processor.new.process
# Output:
# B processing
# A processing
# Processor processing
Including a module multiple times has no additional effect. Ruby ignores subsequent includes of the same module, which can lead to confusion when trying to refresh module definitions during development.
module Testable
def test_method
"version 1"
end
end
class Example
include Testable
include Testable # Ignored
end
# Redefining the module doesn't affect already included classes
module Testable
def test_method
"version 2"
end
end
Example.new.test_method # => "version 1"
# Must create new instances after module redefinition
class NewExample
include Testable
end
NewExample.new.test_method # => "version 2"
Private and protected method visibility transfers through inclusion, but the visibility applies in the context of the including class, not the original module context. This can create unexpected access patterns.
module PrivateMethods
def public_interface
internal_calculation
end
private
def internal_calculation
"calculated result"
end
end
class Calculator
include PrivateMethods
end
calc = Calculator.new
calc.public_interface # => "calculated result"
calc.internal_calculation # => NoMethodError: private method
Constant resolution in included modules follows Ruby's lexical scoping rules, not the method lookup chain. Constants defined in modules remain scoped to those modules even after inclusion.
module Constants
VALUE = "module value"
def get_value
VALUE # Looks in Constants module
end
end
class Container
VALUE = "class value"
include Constants
end
Container.new.get_value # => "module value", not "class value"
Container::VALUE # => "class value"
Constants::VALUE # => "module value"
Instance variables defined in module methods belong to the including object, but their initialization depends on method execution order. Modules cannot reliably assume instance variable state without explicit initialization checks.
module Stateful
def increment
@counter = (@counter || 0) + 1
end
def reset
@counter = 0 # Assumes @counter exists
end
def current_count
@counter || 0 # Safe default handling
end
end
class Counter
include Stateful
end
counter = Counter.new
counter.reset # @counter = 0
counter.current_count # => 0
counter.increment # => 1
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
include(*modules) |
*modules (Module) |
self (Class) |
Mixes modules into the class inheritance chain |
included_modules |
None | Array<Module> |
Returns array of directly included modules |
ancestors |
None | Array<Class,Module> |
Returns complete inheritance chain including included modules |
Callback Hooks
Hook | Parameters | Context | Description |
---|---|---|---|
included(base) |
base (Class) |
Module | Called when module is included in a class |
Method Resolution
Ruby searches for methods in this order when a method is called on an object:
- Singleton methods of the object
- Methods defined in the object's class
- Methods from modules included in the class (reverse inclusion order)
- Methods defined in the superclass
- Methods from modules included in the superclass (reverse inclusion order)
- Continue up the inheritance chain
Visibility Rules
Visibility | Include Behavior | Access Rules |
---|---|---|
public |
Becomes public in including class | Accessible from anywhere |
protected |
Becomes protected in including class | Accessible within class hierarchy |
private |
Becomes private in including class | Only accessible within same object |
Exception Types
Exception | Cause | Resolution |
---|---|---|
TypeError |
Attempting to include a class instead of module | Use inheritance or convert to module |
ArgumentError |
Including with no arguments | Provide at least one module argument |
Common Patterns
Namespace Module Pattern
module MyApp
module Helpers
def format_currency(amount)
"$#{amount}"
end
end
end
class Invoice
include MyApp::Helpers
end
Mixin with Configuration
module Trackable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def track_with(tracker)
@tracker = tracker
end
end
end
Conditional Method Definition
module OptionalFeature
def self.included(base)
if defined?(Rails)
base.send(:include, RailsSpecific)
end
end
end
Debugging Methods
Method | Returns | Usage |
---|---|---|
Module.nesting |
Array<Module> |
Current lexical nesting |
method(:name).source_location |
[String, Integer] |
File and line where method defined |
method(:name).owner |
Module |
Module or class that defines the method |