CrackedRuby logo

CrackedRuby

extend Method

Overview

The extend method adds module methods as singleton methods to objects in Ruby. When an object extends a module, the module's instance methods become singleton methods available directly on that object. This mechanism provides a way to add functionality to individual objects without modifying their class or creating inheritance relationships.

Ruby implements extend through the singleton class mechanism. Each object has a hidden singleton class that stores methods specific to that object. The extend method inserts the module into the singleton class's ancestor chain, making the module's methods available as singleton methods.

The extend method operates at both the instance and class level. When called on an instance, it adds methods available only to that specific object. When called on a class, it adds class methods to that class, since classes are objects in Ruby.

module Greetings
  def hello
    "Hello from #{self}"
  end
end

obj = Object.new
obj.extend(Greetings)
obj.hello
# => "Hello from #<Object:0x007f8b8c0a1234>"

The method lookup follows Ruby's standard chain but includes the singleton class. Ruby searches the singleton class before the object's regular class hierarchy, ensuring extended methods take precedence over instance methods with the same name.

class Person
  def introduce
    "I'm a person"
  end
end

module VIP
  def introduce
    "I'm a VIP person"
  end
end

person = Person.new
person.introduce
# => "I'm a person"

person.extend(VIP)
person.introduce
# => "I'm a VIP person"

Classes commonly use extend in their class definitions to add class methods. This pattern creates a clean separation between instance functionality and class-level utilities.

module Trackable
  def track_instances
    @instance_count ||= 0
  end

  def increment_count
    @instance_count = track_instances + 1
  end
end

class User
  extend Trackable

  def initialize
    self.class.increment_count
  end
end

User.track_instances
# => 0

Basic Usage

The extend method accepts one or more module arguments and returns the receiver object. Multiple modules can be extended simultaneously, with later modules taking precedence in the method lookup chain.

module Authentication
  def authenticate
    "User authenticated"
  end
end

module Authorization
  def authorize
    "User authorized"
  end
end

user = Object.new
user.extend(Authentication, Authorization)
user.authenticate
# => "User authenticated"
user.authorize
# => "User authorized"

Class-level extension creates class methods accessible without instantiating objects. This pattern appears frequently in Ruby libraries and frameworks for configuration and utility methods.

module Configurable
  def configure
    @config ||= {}
  end

  def set_option(key, value)
    configure[key] = value
  end

  def get_option(key)
    configure[key]
  end
end

class Database
  extend Configurable
end

Database.set_option(:host, 'localhost')
Database.get_option(:host)
# => "localhost"

The extend method works with modules containing constants, but the constants remain in the module's namespace. Extended objects cannot access these constants directly through constant resolution.

module Constants
  API_VERSION = "v1"

  def version
    API_VERSION
  end
end

client = Object.new
client.extend(Constants)
client.version
# => "v1"

# This raises NameError
# client::API_VERSION

Module methods that reference self operate on the extended object. This behavior enables modules to provide functionality that adapts to different types of receivers.

module Identifiable
  def object_info
    "#{self.class.name}: #{self.object_id}"
  end

  def type_check
    self.is_a?(String) ? "String object" : "Other object"
  end
end

str = "hello"
str.extend(Identifiable)
str.object_info
# => "String: 70368527942460"
str.type_check
# => "String object"

num = 42
num.extend(Identifiable)
num.type_check
# => "Other object"

Extended modules can access instance variables on the receiver object. The module methods operate within the context of the extended object, allowing them to read and modify the object's state.

module StateManager
  def set_state(value)
    @state = value
  end

  def get_state
    @state
  end

  def state_present?
    !@state.nil?
  end
end

obj = Object.new
obj.extend(StateManager)
obj.set_state("active")
obj.get_state
# => "active"
obj.state_present?
# => true

Advanced Usage

The extend method integrates with Ruby's hook methods, triggering extended callbacks when modules are extended onto objects. These callbacks enable modules to perform initialization or setup tasks specific to extension scenarios.

module Trackable
  def self.extended(base)
    base.instance_variable_set(:@extended_at, Time.now)
    puts "#{base} extended with Trackable at #{Time.now}"
  end

  def extension_time
    @extended_at
  end

  def time_since_extension
    Time.now - @extended_at
  end
end

obj = Object.new
obj.extend(Trackable)
# Output: #<Object:0x007f8b8c0a1234> extended with Trackable at 2023-12-07 10:30:45
sleep(1)
obj.time_since_extension
# => 1.002341

Dynamic extension based on runtime conditions creates flexible object behavior. Objects can acquire different capabilities based on configuration, user roles, or application state.

module AdminFeatures
  def delete_all_data
    "Dangerous admin operation executed"
  end

  def view_system_logs
    "System logs displayed"
  end
end

module UserFeatures
  def view_profile
    "User profile displayed"
  end

  def update_preferences
    "Preferences updated"
  end
end

class Session
  attr_reader :role

  def initialize(role)
    @role = role
    extend_capabilities
  end

  private

  def extend_capabilities
    case @role
    when :admin
      extend(AdminFeatures)
    when :user
      extend(UserFeatures)
    end
  end
end

admin_session = Session.new(:admin)
admin_session.delete_all_data
# => "Dangerous admin operation executed"

user_session = Session.new(:user)
user_session.view_profile
# => "User profile displayed"

Chained extension with module composition creates complex behavior hierarchies. Modules can extend themselves with other modules, creating layered functionality that builds upon base capabilities.

module Loggable
  def log_message(message)
    puts "[LOG] #{Time.now}: #{message}"
  end
end

module Timestampable
  def self.extended(base)
    base.extend(Loggable)
  end

  def with_timestamp(&block)
    start_time = Time.now
    log_message("Operation started")
    result = block.call
    log_message("Operation completed in #{Time.now - start_time} seconds")
    result
  end
end

processor = Object.new
processor.extend(Timestampable)

processor.with_timestamp do
  sleep(0.1)
  "Processing complete"
end
# Output: [LOG] 2023-12-07 10:30:45: Operation started
# Output: [LOG] 2023-12-07 10:30:45: Operation completed in 0.102 seconds
# => "Processing complete"

Conditional method availability through extension enables feature flags and progressive enhancement. Objects can acquire methods based on available dependencies or feature toggles.

module DatabaseFeatures
  def save_to_database
    "Data saved to database"
  end

  def load_from_database
    "Data loaded from database"
  end
end

module CacheFeatures
  def save_to_cache
    "Data cached"
  end

  def load_from_cache
    "Data retrieved from cache"
  end
end

class DataManager
  def initialize(options = {})
    extend(DatabaseFeatures) if options[:database_enabled]
    extend(CacheFeatures) if options[:cache_enabled]
  end

  def available_methods
    methods.grep(/save|load/)
  end
end

full_manager = DataManager.new(database_enabled: true, cache_enabled: true)
full_manager.available_methods
# => [:save_to_database, :load_from_database, :save_to_cache, :load_from_cache]

basic_manager = DataManager.new
basic_manager.available_methods
# => []

Module hierarchies with extend create sophisticated method resolution chains. Modules can extend other modules before being extended themselves, establishing complex inheritance-like relationships.

module BaseValidation
  def validate_present?
    !@value.nil? && !@value.empty?
  end
end

module NumericValidation
  extend BaseValidation

  def self.extended(base)
    base.extend(BaseValidation)
  end

  def validate_numeric?
    validate_present? && @value.is_a?(Numeric)
  end

  def validate_positive?
    validate_numeric? && @value > 0
  end
end

validator = Object.new
validator.instance_variable_set(:@value, 42)
validator.extend(NumericValidation)

validator.validate_present?
# => true
validator.validate_positive?
# => true

Common Pitfalls

Extended methods cannot access the module's constants through normal constant resolution. Constants remain in the module's namespace and require explicit qualification or helper methods to access them from extended objects.

module Configuration
  DEFAULT_TIMEOUT = 30
  API_ENDPOINT = "https://api.example.com"

  def timeout
    # This works - explicit module reference
    Configuration::DEFAULT_TIMEOUT
  end

  def endpoint
    # This also works
    self.class.const_get("#{self.class}::API_ENDPOINT") rescue API_ENDPOINT
  end

  def bad_timeout
    # This raises NameError
    DEFAULT_TIMEOUT
  end
end

client = Object.new
client.extend(Configuration)
client.timeout
# => 30

# This raises NameError: uninitialized constant
# client.bad_timeout

The extend method creates singleton methods that cannot be inherited by subclasses. Unlike regular inheritance, extension affects only the specific object or class that performs the extension.

module Utilities
  def helper_method
    "Helper functionality"
  end
end

class Parent
  extend Utilities
end

class Child < Parent
end

Parent.helper_method
# => "Helper functionality"

# This raises NoMethodError
# Child.helper_method

# Child must extend the module explicitly
Child.extend(Utilities)
Child.helper_method
# => "Helper functionality"

Method name conflicts between extended modules follow last-extended-wins precedence, potentially masking intended functionality. Later extensions override earlier ones without warning.

module ModuleA
  def process
    "Processing in Module A"
  end
end

module ModuleB
  def process
    "Processing in Module B"
  end
end

obj = Object.new
obj.extend(ModuleA)
obj.process
# => "Processing in Module A"

obj.extend(ModuleB)
obj.process
# => "Processing in Module B"

# ModuleA's process method is now inaccessible

Extended modules modify the singleton class permanently, even if the extension seems temporary. There is no built-in mechanism to "unextend" a module from an object.

module TemporaryFeature
  def temporary_method
    "This method seems temporary"
  end
end

obj = Object.new
obj.extend(TemporaryFeature)

# Method persists on the object
obj.temporary_method
# => "This method seems temporary"

# No way to remove the extension
# The singleton class permanently includes the module
obj.singleton_class.ancestors.include?(TemporaryFeature)
# => true

Block expectations with extend can cause confusion since extend does not accept blocks like include sometimes does in custom implementations. Attempting to pass a block to extend ignores the block silently.

module Configurable
  def configure
    yield if block_given?
  end
end

obj = Object.new

# This extends the module but ignores the block completely
obj.extend(Configurable) do
  puts "This block is ignored"
end

# No output from the block above
obj.configure do
  puts "This block works correctly"
end
# Output: This block works correctly

Extended modules sharing state through class variables or module variables can create unexpected coupling between seemingly unrelated objects.

module Counter
  @@count = 0

  def increment
    @@count += 1
  end

  def current_count
    @@count
  end
end

obj1 = Object.new
obj1.extend(Counter)
obj1.increment
obj1.current_count
# => 1

obj2 = Object.new
obj2.extend(Counter)
obj2.current_count
# => 1 (shares state with obj1)

obj2.increment
obj1.current_count
# => 2 (both objects affect the same counter)

Method visibility rules with extend can produce unexpected behavior. Private and protected methods in modules become public singleton methods when extended, breaking encapsulation expectations.

module SecretMethods
  def public_method
    "Public access"
  end

  private

  def secret_method
    "This should be private"
  end
end

obj = Object.new
obj.extend(SecretMethods)

obj.public_method
# => "Public access"

# This works despite secret_method being private in the module
obj.secret_method
# => "This should be private"

# The method is now public on the singleton class
obj.public_methods.include?(:secret_method)
# => true

Reference

Core Methods

Method Parameters Returns Description
Object#extend(*modules) modules (Module, ...) self Adds module methods as singleton methods to the receiver
Module.extended(base) base (Object) - Hook method called when module is extended onto an object

Extension Patterns

Pattern Usage Example
Instance Extension obj.extend(Module) Adds methods to specific object
Class Extension Class.extend(Module) Adds class methods to class
Multiple Extension obj.extend(Mod1, Mod2) Extends multiple modules at once
Conditional Extension obj.extend(Mod) if condition Extends based on runtime condition

Method Lookup Chain

When an object extends a module, Ruby modifies the method lookup chain:

  1. Singleton Class - Extended module methods
  2. Object's Class - Regular instance methods
  3. Superclass Chain - Parent class methods
  4. BasicObject - Root object methods

Hook Methods

Hook Trigger Parameters Purpose
extended Module extended onto object base (receiver object) Initialization after extension
method_added Method added to module method_name (Symbol) Track method additions
singleton_method_added Singleton method added method_name (Symbol) Track singleton additions

Visibility Behavior

Module Method Visibility Extended Method Visibility Notes
public public Standard behavior
private public Visibility changes during extension
protected public Visibility changes during extension

Common Extend vs Include Comparison

Aspect extend include
Method Type Singleton methods Instance methods
Target Individual objects All instances of class
Inheritance Not inherited Inherited by subclasses
Lookup Chain Singleton class Instance class hierarchy
Multiple Modules Last wins precedence First wins precedence

Extended Object Inspection

Method Returns Purpose
obj.singleton_class.ancestors Array of modules/classes Shows singleton class hierarchy
obj.singleton_methods Array of method symbols Lists singleton methods
obj.method(:name).owner Module/Class Shows where method is defined
Module === obj.singleton_class Boolean Checks if module extended

Performance Characteristics

Operation Complexity Notes
Extension O(1) Creates singleton class if needed
Method Lookup O(n) Linear through ancestor chain
Multiple Extensions O(m) Linear with number of modules
Singleton Class Creation O(1) One-time cost per object

Memory Implications

  • Each extended object gets a singleton class
  • Singleton classes increase memory overhead
  • Extended modules remain in memory while objects exist
  • Method caches invalidated during extension

Thread Safety Considerations

  • Extension operations are not atomic
  • Concurrent extensions on same object may cause issues
  • Module constants remain thread-safe
  • Shared module state requires synchronization