Overview
Ruby provides extensive runtime code modification capabilities through its metaprogramming features. Classes and modules remain open throughout program execution, allowing developers to add, remove, or modify methods and constants dynamically. This flexibility enables powerful patterns like monkey patching, dynamic method creation, and runtime API adaptation.
The core mechanism operates through Ruby's object model where classes are themselves objects that can be modified. Every class and module maintains method tables that can be altered at runtime using built-in methods like define_method
, alias_method
, remove_method
, and undef_method
.
class User
def name
@name
end
end
# Add method at runtime
User.class_eval do
def email
@email
end
end
user = User.new
user.respond_to?(:email) # => true
Ruby distinguishes between several modification approaches. Class reopening allows adding methods to existing classes. Method aliasing creates new names for existing methods while preserving originals. Method removal deletes methods from the inheritance chain. Method undefinition prevents method calls entirely, even from superclasses.
class String
alias_method :original_reverse, :reverse
def reverse
"Modified: #{original_reverse}"
end
end
"hello".reverse # => "Modified: olleh"
Module inclusion, prepending, and extension provide additional runtime modification vectors. Including modules adds instance methods, prepending inserts modules earlier in the method lookup chain, and extending adds singleton methods to specific objects.
module Timestamps
def created_at
@created_at ||= Time.now
end
end
class Article
end
# Runtime module inclusion
Article.include(Timestamps)
Article.new.created_at # => 2025-08-31 ...
Basic Usage
Method definition at runtime uses define_method
to create methods programmatically. This approach proves useful for generating similar methods or creating methods based on configuration data.
class APIClient
%w[get post put delete].each do |verb|
define_method(verb) do |path, params = {}|
request(verb.upcase, path, params)
end
end
private
def request(method, path, params)
"#{method} #{path} with #{params}"
end
end
client = APIClient.new
client.get("/users", limit: 10) # => "GET /users with {:limit=>10}"
Class reopening modifies existing classes by defining additional methods or overriding existing ones. This technique commonly appears in library extensions and framework customizations.
class Hash
def deep_merge(other_hash)
merge(other_hash) do |key, old_val, new_val|
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
old_val.deep_merge(new_val)
else
new_val
end
end
end
end
hash1 = { a: { b: 1, c: 2 } }
hash2 = { a: { c: 3, d: 4 } }
hash1.deep_merge(hash2) # => { a: { b: 1, c: 3, d: 4 } }
Method aliasing preserves original behavior while enabling method enhancement or decoration. The pattern creates a backup of the original method before redefining it.
class Logger
def log(message)
puts message
end
alias_method :original_log, :log
def log(message)
original_log("[#{Time.now}] #{message}")
end
end
logger = Logger.new
logger.log("Application started") # => [2025-08-31 ...] Application started
Runtime constant modification changes class and module constants during execution. Ruby issues warnings for constant redefinition but allows the operation.
class Config
API_VERSION = "v1"
end
puts Config::API_VERSION # => "v1"
# Suppress warnings for demonstration
Config.send(:remove_const, :API_VERSION)
Config.const_set(:API_VERSION, "v2")
puts Config::API_VERSION # => "v2"
Advanced Usage
Dynamic method creation based on external data sources enables flexible API generation. This pattern commonly appears in ORM libraries and configuration-driven applications.
class DynamicModel
def initialize(schema)
@data = {}
schema.each do |field, type|
create_accessor(field, type)
create_validator(field, type) if type[:required]
end
end
private
def create_accessor(field, type)
define_singleton_method(field) do
@data[field]
end
define_singleton_method("#{field}=") do |value|
@data[field] = convert_type(value, type[:class])
end
end
def create_validator(field, type)
define_singleton_method("#{field}_valid?") do
value = @data[field]
!value.nil? && value.is_a?(type[:class])
end
end
def convert_type(value, klass)
case klass
when Integer then value.to_i
when String then value.to_s
when Float then value.to_f
else value
end
end
end
schema = {
name: { class: String, required: true },
age: { class: Integer },
email: { class: String, required: true }
}
user = DynamicModel.new(schema)
user.name = "John"
user.age = "30"
user.name_valid? # => true
user.age # => 30 (converted to Integer)
Method chain interception modifies method behavior throughout inheritance hierarchies. This advanced technique enables aspect-oriented programming and cross-cutting concerns.
module MethodInterceptor
def self.included(base)
base.extend(ClassMethods)
base.class_variable_set(:@@intercepted_methods, {})
end
module ClassMethods
def intercept_method(method_name, &block)
original_method = "original_#{method_name}"
alias_method original_method, method_name
define_method(method_name) do |*args, **kwargs|
result = instance_exec(*args, **kwargs, &block)
if result == :continue
send(original_method, *args, **kwargs)
else
result
end
end
class_variable_get(:@@intercepted_methods)[method_name] = original_method
end
end
end
class Calculator
include MethodInterceptor
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
intercept_method :add do |a, b|
if a.negative? || b.negative?
raise ArgumentError, "Negative numbers not allowed"
end
:continue # Proceed with original method
end
intercept_method :multiply do |a, b|
return 0 if a.zero? || b.zero?
:continue
end
end
calc = Calculator.new
calc.add(5, 3) # => 8
calc.multiply(0, 5) # => 0 (intercepted)
calc.add(-1, 2) # => ArgumentError
Conditional method definition creates methods based on runtime conditions or feature flags. This pattern enables environment-specific behavior and progressive feature rollouts.
class FeatureFlag
@flags = {}
class << self
def enable(flag)
@flags[flag] = true
end
def enabled?(flag)
@flags[flag] == true
end
end
end
class PaymentProcessor
def process_payment(amount)
"Processing $#{amount}"
end
# Conditionally define enhanced method
if FeatureFlag.enabled?(:fraud_detection)
alias_method :process_payment_basic, :process_payment
def process_payment(amount)
return "FRAUD DETECTED" if amount > 10000
process_payment_basic(amount)
end
end
# Define debug methods only in development
if ENV['RACK_ENV'] == 'development'
def debug_payment_flow(amount)
{
amount: amount,
fraud_check: amount > 10000,
processor: self.class.name
}
end
end
end
FeatureFlag.enable(:fraud_detection)
processor = PaymentProcessor.new
processor.process_payment(15000) # => "FRAUD DETECTED"
Common Pitfalls
Method redefinition without aliasing destroys original functionality permanently. This creates brittle code when multiple libraries modify the same methods.
# PROBLEMATIC: Direct override without backup
class String
def reverse
upcase # Original reverse behavior lost
end
end
"hello".reverse # => "HELLO" (not "olleh")
# BETTER: Alias first, then override
class String
alias_method :original_reverse, :reverse
def reverse
"Reversed: #{original_reverse}"
end
end
Constant modification creates memory leaks when constants reference large objects. Ruby maintains constant history, preventing garbage collection of previous values.
# PROBLEMATIC: Constant reassignment with large objects
class DataCache
LARGE_DATASET = Array.new(100_000) { rand }
end
# Each reassignment keeps previous array in memory
5.times do |i|
DataCache.send(:remove_const, :LARGE_DATASET)
DataCache.const_set(:LARGE_DATASET, Array.new(100_000) { rand })
end
# BETTER: Use class variables or instance variables for mutable data
class DataCache
@@dataset = nil
def self.dataset=(data)
@@dataset = data
end
def self.dataset
@@dataset
end
end
Method visibility changes during runtime create inconsistent APIs when not carefully managed across inheritance chains.
class BaseService
private
def authenticate
"authenticated"
end
end
class PublicService < BaseService
# PROBLEMATIC: Making private method public
public :authenticate
end
class RestrictedService < BaseService
# authenticate remains private
end
# Inconsistent behavior across subclasses
PublicService.new.authenticate # => "authenticated"
RestrictedService.new.authenticate # => NoMethodError
Dynamic method creation without proper name validation enables method name injection attacks and creates debugging difficulties.
# PROBLEMATIC: Unsafe method creation
class DynamicClass
def create_method(name, value)
define_singleton_method(name) { value }
end
end
obj = DynamicClass.new
# Malicious input could create problematic methods
obj.create_method("class", "hacked") # Overrides #class method
# BETTER: Validate method names
class SafeDynamicClass
VALID_METHOD_NAME = /\A[a-z_][a-z0-9_]*[?!]?\z/
def create_method(name, value)
unless name.match?(VALID_METHOD_NAME)
raise ArgumentError, "Invalid method name: #{name}"
end
if respond_to?(name) || Object.instance_methods.include?(name.to_sym)
raise ArgumentError, "Method already exists: #{name}"
end
define_singleton_method(name) { value }
end
end
Module inclusion order affects method resolution and can mask intended behavior when modules contain same-named methods.
module A
def greet
"Hello from A"
end
end
module B
def greet
"Hello from B"
end
end
class TestClass
include A
include B # B's methods override A's methods
end
TestClass.new.greet # => "Hello from B"
# Use module prepending for explicit ordering
class OrderedClass
include A
prepend B # B comes first in method lookup
end
OrderedClass.ancestors # => [B, OrderedClass, A, ...]
Thread Safety & Concurrency
Runtime method modification in concurrent environments creates race conditions when multiple threads simultaneously modify the same class. Method definition and removal operations are not atomic, potentially creating inconsistent method tables.
require 'thread'
class ThreadSafeModifier
@modification_mutex = Mutex.new
class << self
def safe_define_method(klass, method_name, &block)
@modification_mutex.synchronize do
klass.define_method(method_name, &block)
end
end
def safe_remove_method(klass, method_name)
@modification_mutex.synchronize do
klass.remove_method(method_name) if klass.method_defined?(method_name)
end
end
end
end
# Safe concurrent method modification
threads = 10.times.map do |i|
Thread.new do
ThreadSafeModifier.safe_define_method(String, "method_#{i}") do
"Thread #{i} method"
end
end
end
threads.each(&:join)
String.instance_methods.grep(/method_\d+/).size # => 10 (all methods created)
Singleton method modification on shared objects requires careful synchronization to prevent method table corruption during concurrent access.
class ConcurrentSingletonManager
def initialize
@mutex = Mutex.new
@objects = {}
end
def add_singleton_method(object_id, method_name, &block)
@mutex.synchronize do
object = @objects[object_id]
return unless object
object.define_singleton_method(method_name, &block)
end
end
def register_object(id, object)
@mutex.synchronize do
@objects[id] = object
end
end
def call_singleton_method(object_id, method_name, *args)
object = nil
@mutex.synchronize { object = @objects[object_id] }
object&.send(method_name, *args) if object&.respond_to?(method_name)
end
end
manager = ConcurrentSingletonManager.new
shared_object = Object.new
manager.register_object(:shared, shared_object)
# Safe concurrent singleton method operations
threads = 5.times.map do |i|
Thread.new do
manager.add_singleton_method(:shared, "concurrent_method_#{i}") do
"Result from thread #{i}"
end
end
end
threads.each(&:join)
Constant modification during concurrent execution requires explicit synchronization since Ruby's constant warning mechanism is not thread-safe and can produce garbled output.
class ThreadSafeConstants
@constant_mutex = Mutex.new
class << self
def modify_constant(klass, name, value)
@constant_mutex.synchronize do
# Capture warnings to prevent garbled output
original_stderr = $stderr
$stderr = StringIO.new
begin
klass.send(:remove_const, name) if klass.const_defined?(name)
klass.const_set(name, value)
ensure
$stderr = original_stderr
end
end
end
def read_constant(klass, name)
@constant_mutex.synchronize do
klass.const_get(name) if klass.const_defined?(name)
end
end
end
end
# Concurrent constant modification without warning pollution
class ConfigManager
API_ENDPOINT = "https://api.example.com"
end
threads = 3.times.map do |i|
Thread.new do
ThreadSafeConstants.modify_constant(
ConfigManager,
:API_ENDPOINT,
"https://api-v#{i}.example.com"
)
end
end
threads.each(&:join)
puts ThreadSafeConstants.read_constant(ConfigManager, :API_ENDPOINT)
Reference
Core Modification Methods
Method | Parameters | Returns | Description |
---|---|---|---|
define_method(name, &block) |
name (Symbol/String), block |
Symbol | Creates instance method with given name and implementation |
alias_method(new_name, old_name) |
new_name , old_name (Symbol/String) |
Module | Creates method alias preserving original |
remove_method(name) |
name (Symbol/String) |
Module | Removes method from current class, allows inheritance |
undef_method(name) |
name (Symbol/String) |
Module | Prevents method calls completely, ignores inheritance |
method_defined?(name) |
name (Symbol/String) |
Boolean | Checks if instance method exists |
private_method_defined?(name) |
name (Symbol/String) |
Boolean | Checks if private instance method exists |
Class and Module Evaluation
Method | Parameters | Returns | Description |
---|---|---|---|
class_eval(string/&block) |
string or block, optional filename/line |
Mixed | Evaluates code in class context |
module_eval(string/&block) |
string or block, optional filename/line |
Mixed | Evaluates code in module context |
instance_eval(string/&block) |
string or block, optional filename/line |
Mixed | Evaluates code in object's singleton context |
class_exec(*args, &block) |
arguments and block | Mixed | Evaluates block in class context with arguments |
module_exec(*args, &block) |
arguments and block | Mixed | Evaluates block in module context with arguments |
Constant Manipulation
Method | Parameters | Returns | Description |
---|---|---|---|
const_set(name, value) |
name (Symbol/String), value |
Object | Sets constant to specified value |
const_get(name, inherit=true) |
name (Symbol/String), inherit flag |
Object | Retrieves constant value |
const_defined?(name, inherit=true) |
name (Symbol/String), inherit flag |
Boolean | Checks constant existence |
remove_const(name) |
name (Symbol/String) |
Object | Removes constant and returns its value |
constants(inherit=true) |
inherit flag | Array | Lists defined constants |
Method Visibility Control
Method | Parameters | Returns | Description |
---|---|---|---|
private(*method_names) |
method names (Symbol/String) | Module | Makes methods private |
protected(*method_names) |
method names (Symbol/String) | Module | Makes methods protected |
public(*method_names) |
method names (Symbol/String) | Module | Makes methods public |
private_class_method(*method_names) |
method names (Symbol/String) | Module | Makes class methods private |
public_class_method(*method_names) |
method names (Symbol/String) | Module | Makes class methods public |
Module Inclusion Operations
Method | Parameters | Returns | Description |
---|---|---|---|
include(*modules) |
modules | Self | Adds modules as ancestors |
prepend(*modules) |
modules | Self | Inserts modules before self in ancestors |
extend(*modules) |
modules | Object | Adds module methods as singleton methods |
included_modules |
none | Array | Returns included modules |
ancestors |
none | Array | Returns ancestor chain including modules |
Singleton Method Operations
Method | Parameters | Returns | Description |
---|---|---|---|
define_singleton_method(name, &block) |
name (Symbol/String), block |
Symbol | Defines method on specific object |
singleton_methods(all=true) |
include inherited flag | Array | Lists singleton methods |
singleton_class |
none | Class | Returns object's singleton class |
Hook Methods for Runtime Modification
Method | Context | Parameters | Description |
---|---|---|---|
method_added(method_name) |
Class/Module | method name | Called when instance method added |
method_removed(method_name) |
Class/Module | method name | Called when instance method removed |
method_undefined(method_name) |
Class/Module | method name | Called when instance method undefined |
singleton_method_added(method_name) |
Object | method name | Called when singleton method added |
const_missing(name) |
Class/Module | constant name | Called when constant not found |
Common Error Types
Exception | Trigger Condition | Prevention Strategy |
---|---|---|
NameError |
Method or constant not found | Use defined? or respond_to? checks |
ArgumentError |
Wrong number of method arguments | Validate arguments before method calls |
NoMethodError |
Method called on object that doesn't respond | Check with respond_to? first |
RuntimeError |
Method redefinition conflicts | Use proper aliasing and namespacing |
FrozenError |
Modification of frozen object | Check frozen? before modifications |