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:
- Singleton Class - Extended module methods
- Object's Class - Regular instance methods
- Superclass Chain - Parent class methods
- 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