CrackedRuby logo

CrackedRuby

include Method

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:

  1. Singleton methods of the object
  2. Methods defined in the object's class
  3. Methods from modules included in the class (reverse inclusion order)
  4. Methods defined in the superclass
  5. Methods from modules included in the superclass (reverse inclusion order)
  6. 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