CrackedRuby logo

CrackedRuby

included and extended

Overview

Ruby provides two module callback methods, included and extended, that execute automatically when a module gets mixed into a class or object. The included method triggers when a module is included via include, while extended triggers when a module is mixed in via extend. These callbacks receive the target class or object as an argument, creating opportunities for dynamic behavior modification and metaprogramming.

When Ruby encounters include ModuleName in a class definition, it first adds the module's instance methods to the class, then calls ModuleName.included(TargetClass) if the method exists. Similarly, extend ModuleName adds the module's methods as singleton methods to the target, then calls ModuleName.extended(TargetObject).

module Trackable
  def self.included(base)
    puts "#{self} included in #{base}"
  end
  
  def track_action
    puts "Action tracked"
  end
end

class User
  include Trackable
end
# Output: Trackable included in User

The callback methods execute in the context of the module itself, not the target class. This distinction affects which variables and methods are accessible within the callback. The base parameter represents the class or object receiving the module's functionality.

module Configurable
  @default_config = { timeout: 30 }
  
  def self.extended(obj)
    obj.instance_variable_set(:@config, @default_config.dup)
  end
  
  def configure(options = {})
    @config.merge!(options)
  end
end

api_client = Object.new
api_client.extend(Configurable)
# Configurable.extended(api_client) called automatically

These callbacks form the foundation for many Ruby design patterns including class-level DSLs, automatic method generation, and registration systems. The timing of execution makes them particularly useful for modifying the target class's structure or establishing initial state.

Basic Usage

Module callbacks typically modify the target class by adding class methods, defining constants, or establishing instance variable defaults. The most common pattern involves adding both instance and class methods from a single module inclusion.

module Timestampable
  def self.included(base)
    base.extend(ClassMethods)
    base.class_eval do
      attr_accessor :created_at, :updated_at
    end
  end
  
  module ClassMethods
    def with_timestamps
      all.select { |obj| obj.created_at && obj.updated_at }
    end
  end
  
  def touch
    @updated_at = Time.now
  end
end

class Document
  include Timestampable
  
  def initialize
    @created_at = Time.now
  end
end

doc = Document.new
doc.touch
Document.with_timestamps  # Class method available

The extended callback works differently since extend adds methods as singleton methods rather than instance methods. This makes it ideal for adding functionality to specific objects or creating module-based configuration systems.

module Cacheable
  def self.extended(obj)
    obj.instance_variable_set(:@cache, {})
    def obj.cache_key_for(method_name)
      "#{self.class.name}##{method_name}"
    end
  end
  
  def cached_method(name, &block)
    key = cache_key_for(name)
    @cache[key] ||= instance_eval(&block)
  end
end

class ExpensiveCalculator
  extend Cacheable
  
  def fibonacci(n)
    cached_method("fibonacci_#{n}") do
      n <= 1 ? n : fibonacci(n-1) + fibonacci(n-2)
    end
  end
end

Registration patterns commonly use these callbacks to maintain lists of classes that include specific modules. This approach enables automatic discovery and processing of related classes.

module Serializable
  @serializable_classes = []
  
  def self.included(base)
    @serializable_classes << base
    base.extend(ClassMethods)
  end
  
  def self.all_serializable_classes
    @serializable_classes
  end
  
  module ClassMethods
    def serializable_fields(*fields)
      @serializable_fields = fields
    end
    
    def get_serializable_fields
      @serializable_fields || []
    end
  end
  
  def serialize
    fields = self.class.get_serializable_fields
    fields.each_with_object({}) do |field, hash|
      hash[field] = send(field)
    end
  end
end

class User
  include Serializable
  serializable_fields :name, :email
  
  attr_accessor :name, :email, :password
end

class Product
  include Serializable
  serializable_fields :title, :price
  
  attr_accessor :title, :price, :internal_code
end

Serializable.all_serializable_classes
# => [User, Product]

Both callbacks can access and modify class-level instance variables, making them suitable for maintaining module-specific state across multiple classes. The key difference lies in how the methods become available: include creates instance methods while extend creates singleton methods on the target.

Advanced Usage

Module callbacks enable sophisticated metaprogramming patterns through dynamic method generation and class structure modification. These techniques often involve creating domain-specific languages or implementing complex inheritance hierarchies.

module Validatable
  def self.included(base)
    base.extend(ClassMethods)
    base.instance_variable_set(:@validations, {})
    base.instance_variable_set(:@validation_errors, [])
  end
  
  module ClassMethods
    def validates(field, **options)
      @validations[field] = options
      
      # Generate custom validation methods
      options.each do |validator, constraint|
        method_name = "validate_#{field}_#{validator}"
        define_validation_method(method_name, field, validator, constraint)
      end
    end
    
    def validation_errors
      @validation_errors
    end
    
    private
    
    def define_validation_method(method_name, field, validator, constraint)
      case validator
      when :presence
        define_method(method_name) do
          value = send(field)
          if value.nil? || (value.respond_to?(:empty?) && value.empty?)
            self.class.instance_variable_get(:@validation_errors) << "#{field} cannot be blank"
            false
          else
            true
          end
        end
      when :length
        define_method(method_name) do
          value = send(field)
          return true if value.nil?
          
          min, max = constraint.values_at(:min, :max)
          length = value.length
          
          if min && length < min
            self.class.instance_variable_get(:@validation_errors) << "#{field} is too short"
            false
          elsif max && length > max
            self.class.instance_variable_get(:@validation_errors) << "#{field} is too long"
            false
          else
            true
          end
        end
      end
    end
  end
  
  def validate
    self.class.instance_variable_get(:@validations).each do |field, options|
      options.each do |validator, _|
        method_name = "validate_#{field}_#{validator}"
        send(method_name)
      end
    end
    self.class.validation_errors.empty?
  end
end

class Account
  include Validatable
  
  validates :username, presence: true, length: { min: 3, max: 20 }
  validates :email, presence: true
  
  attr_accessor :username, :email
end

account = Account.new
account.validate  # false
Account.validation_errors
# => ["username cannot be blank", "email cannot be blank"]

Callback methods can implement complex delegation patterns by dynamically creating methods that forward calls to composed objects. This technique supports building flexible architectures without traditional inheritance.

module Delegatable
  def self.included(base)
    base.extend(ClassMethods)
    base.instance_variable_set(:@delegated_methods, {})
  end
  
  module ClassMethods
    def delegate(*methods, to:, prefix: nil, allow_nil: false)
      target_methods = methods.flatten
      @delegated_methods[to] = target_methods
      
      target_methods.each do |method|
        delegated_method_name = prefix ? "#{prefix}_#{method}" : method
        
        define_method(delegated_method_name) do
          target_object = send(to)
          
          if target_object.nil?
            if allow_nil
              nil
            else
              raise "#{to} is nil, cannot delegate #{method}"
            end
          else
            target_object.send(method)
          end
        end
      end
    end
    
    def delegated_methods
      @delegated_methods
    end
  end
end

class Order
  include Delegatable
  
  delegate :name, :email, to: :customer
  delegate :total, :currency, to: :payment, prefix: :payment
  delegate :street, :city, to: :shipping_address, allow_nil: true
  
  attr_accessor :customer, :payment, :shipping_address
end

class Customer
  attr_accessor :name, :email
end

class Payment
  attr_accessor :total, :currency
end

order = Order.new
customer = Customer.new
customer.name = "John Doe"
customer.email = "john@example.com"
order.customer = customer

order.name   # "John Doe" - delegated to customer.name
order.email  # "john@example.com" - delegated to customer.email

Module callbacks support building plugin architectures through automatic hook registration and method interception. This pattern enables extending application behavior without modifying core classes.

module Pluggable
  def self.included(base)
    base.extend(ClassMethods)
    base.instance_variable_set(:@hooks, Hash.new { |h, k| h[k] = [] })
    base.instance_variable_set(:@plugins, [])
  end
  
  module ClassMethods
    def install_plugin(plugin_module)
      include plugin_module
      @plugins << plugin_module
      
      # Register plugin hooks
      plugin_module.constants.each do |const_name|
        const_value = plugin_module.const_get(const_name)
        if const_value.respond_to?(:call) && const_name.to_s.end_with?('_HOOK')
          hook_name = const_name.to_s.sub('_HOOK', '').downcase.to_sym
          @hooks[hook_name] << const_value
        end
      end
    end
    
    def trigger_hooks(hook_name, *args)
      @hooks[hook_name].each { |hook| hook.call(*args) }
    end
    
    def installed_plugins
      @plugins
    end
  end
  
  def execute_with_hooks(method_name, *args)
    self.class.trigger_hooks(:before, method_name, *args)
    result = send("#{method_name}_without_hooks", *args)
    self.class.trigger_hooks(:after, method_name, result, *args)
    result
  end
end

module LoggingPlugin
  BEFORE_HOOK = lambda do |method_name, *args|
    puts "Calling #{method_name} with #{args.inspect}"
  end
  
  AFTER_HOOK = lambda do |method_name, result, *args|
    puts "#{method_name} returned #{result.inspect}"
  end
end

class Calculator
  include Pluggable
  
  install_plugin LoggingPlugin
  
  def add(a, b)
    execute_with_hooks(:add, a, b)
  end
  
  def add_without_hooks(a, b)
    a + b
  end
end

calc = Calculator.new
calc.add(5, 3)
# Output:
# Calling add with [5, 3]
# add returned 8

These advanced patterns demonstrate how module callbacks can transform simple inclusion mechanisms into powerful architectural tools for building maintainable and extensible applications.

Common Pitfalls

Module callbacks execute during class definition time, not instance creation time, which creates timing-related issues that catch developers off guard. Code that depends on instance state or runtime configuration may behave unexpectedly.

module Problematic
  def self.included(base)
    # This runs during class definition, not instantiation
    base.class_eval do
      def current_time
        Time.now  # Always returns the time when class was defined
      end
    end
  end
end

class TimestampedClass
  include Problematic
end

# Multiple instances return the same time
obj1 = TimestampedClass.new
sleep(2)
obj2 = TimestampedClass.new
obj1.current_time == obj2.current_time  # true - both return class definition time

The correct approach defers execution until runtime by defining methods that calculate values when called rather than when defined.

module CorrectTiming
  def self.included(base)
    base.class_eval do
      define_method(:current_time) do
        Time.now  # Calculated each time method is called
      end
    end
  end
end

Variable scope within callbacks often confuses developers because the callback executes in the module's context, not the target class context. This affects access to instance variables and local variables.

module ScopeConfusion
  @module_variable = "module scope"
  
  def self.included(base)
    # @module_variable refers to module's instance variable
    puts @module_variable  # "module scope"
    
    # Trying to access base's instance variables requires explicit reference
    base.instance_variable_set(:@class_variable, "class scope")
    
    # Local variables from surrounding scope are not accessible
    local_var = "callback scope"
    base.define_method(:show_variables) do
      # local_var is not accessible here
      # @module_variable is not accessible here unless explicitly set
      @class_variable  # This works, was set by callback
    end
  end
end

Method naming conflicts occur when multiple modules define the same callback methods or when callbacks define methods that clash with existing methods. The last module included wins, potentially overriding previous behavior silently.

module FirstModule
  def self.included(base)
    base.define_method(:process) do
      "First module processing"
    end
  end
end

module SecondModule
  def self.included(base)
    base.define_method(:process) do
      "Second module processing"  # Overwrites FirstModule's process method
    end
  end
end

class Processor
  include FirstModule
  include SecondModule  # SecondModule's process method wins
end

Processor.new.process  # "Second module processing"

Inheritance hierarchies create unexpected callback execution order when modules are included at different class levels. Callbacks execute for each inclusion, but method resolution follows Ruby's standard lookup path.

module TrackingModule
  def self.included(base)
    puts "Tracking included in #{base}"
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def tracked_method
      "tracking from #{self}"
    end
  end
end

class BaseClass
  include TrackingModule  # Callback executes
end

class SubClass < BaseClass
  include TrackingModule  # Callback executes again, methods redefined
end

# Output:
# Tracking included in BaseClass
# Tracking included in SubClass

SubClass.tracked_method  # "tracking from SubClass"
BaseClass.tracked_method  # "tracking from BaseClass"

Class-level instance variable sharing between modules and target classes creates hidden dependencies that break when module internal implementation changes. Modules should not assume target classes maintain specific instance variable structures.

module ProblematicSharing
  def self.included(base)
    # Assumes base class will maintain @items array
    base.instance_variable_set(:@items, [])
    
    base.define_method(:add_item) do |item|
      # Directly manipulates class instance variable
      self.class.instance_variable_get(:@items) << item
    end
  end
end

class Container
  include ProblematicSharing
  
  # Later refactoring changes @items to @elements
  def initialize
    @elements = []  # Module still expects @items
  end
end

container = Container.new
container.add_item("test")  # Creates separate @items array, @elements remains empty

The solution involves establishing clear contracts between modules and target classes, or using accessor methods instead of direct instance variable manipulation.

module BetterSharing
  def self.included(base)
    base.class_eval do
      attr_accessor :items
      
      def initialize(*args)
        super(*args)
        @items = [] unless defined?(@items)
      end
    end
  end
  
  def add_item(item)
    self.items << item  # Uses accessor method
  end
end

Reference

Module Callback Methods

Method Trigger Context Parameters Purpose
self.included(base) include ModuleName Module Target class Execute code when module included
self.extended(object) extend ModuleName Module Target object Execute code when module extended
self.prepended(base) prepend ModuleName Module Target class Execute code when module prepended

Callback Execution Context

Aspect Details
Execution Time Class definition time, not instance creation
Receiver Context Module itself (self refers to module)
Variable Scope Module's scope, not target class scope
Method Definitions Use base.define_method or base.class_eval
Instance Variables Module's instance variables accessible via @var

Common Callback Patterns

Pattern Implementation Use Case
Add Class Methods base.extend(ClassMethods) Add class-level functionality
Define Instance Methods base.define_method(name, &block) Dynamic method creation
Set Instance Variables base.instance_variable_set(:@var, value) Initialize class state
Class Evaluation base.class_eval { code } Execute code in class context
Registration @@registry << base Track classes using module

Method Definition Techniques

Technique Syntax When to Use
define_method base.define_method(:name) { code } Dynamic method names or closures
class_eval string base.class_eval("def name; code; end") String-based method definitions
class_eval block base.class_eval { def name; code; end } Block-based method definitions
module_eval base.module_eval { code } Module context evaluation

Execution Order

Scenario Order
Single Include Include module methods → Call included
Multiple Includes Process in order of inclusion
Inheritance Callbacks execute for each class separately
Nested Modules Inner modules first, outer modules second

Common Method Patterns

# Class method addition pattern
def self.included(base)
  base.extend(ClassMethods)
end

# Instance variable initialization pattern  
def self.included(base)
  base.instance_variable_set(:@collection, [])
end

# Dynamic method generation pattern
def self.included(base)
  base.define_method(:dynamic_method) do |arg|
    "Generated method with #{arg}"
  end
end

# Registration pattern
def self.included(base)
  registered_classes << base
end

# Configuration pattern
def self.extended(obj)
  obj.instance_variable_set(:@config, default_config)
end

Error Conditions

Error Cause Solution
NoMethodError Callback defines methods incorrectly Use proper method definition techniques
NameError Variable scope confusion Explicitly reference target class variables
ArgumentError Callback expects different parameters Check callback method signature
RuntimeError Timing issues with instance state Defer execution to method call time

Debugging Techniques

Issue Debugging Approach
Callback Not Executing Verify callback method name and definition
Wrong Method Context Check if using define_method vs class_eval
Variable Access Problems Print available instance variables in callback
Method Override Issues Check module inclusion order
Inheritance Conflicts Trace method lookup chain with ancestors

Performance Considerations

Consideration Impact Mitigation
Callback Complexity Slows class definition Keep callbacks simple
Method Generation Memory usage for many methods Generate methods conditionally
Class Variable Access Slower than instance variables Cache frequently accessed values
Deep Inheritance Multiple callback executions Design shallow hierarchies