🔍 Introduction
Ruby’s meta-programming capabilities let you write code that writes code, opening doors to DSLs (Check my post on DSL), dynamic behaviours, and DRY abstractions. In this two-part series, we’ll explore:
- Part 1:
class_eval,instance_eval,eval,define_method, andmethod_missing - Part 2: Additional metaprogramming methods like
define_singleton_method,module_eval,send, and more
🏷️ Part 1: Core Metaprogramming Methods
🔨 1. What is class_eval?
class_eval?class_eval is a method that allows you to evaluate a block of code or a string within the context of a class. It’s part of Ruby’s metaprogramming toolkit and is inherited from the Module class.
Basic Syntax
class MyClass
# class definition
end
MyClass.class_eval do
# code executed in the context of MyClass
def new_method
puts "Hello from class_eval!"
end
end
Key Features
1. Context Execution
The code inside class_eval is executed as if it were written directly inside the class definition:
class Example
def existing_method
puts "I exist"
end
end
Example.class_eval do
def dynamic_method
puts "I was added dynamically!"
end
end
# Now you can use the new method
obj = Example.new
obj.dynamic_method # => "I was added dynamically!"
2. String Evaluation
You can also pass a string instead of a block:
class MyClass
end
MyClass.class_eval("def hello; puts 'Hello!'; end")
3. Access to Class Variables and Constants
The evaluated code has access to the class’s variables and constants:
class Calculator
OPERATIONS = [:add, :subtract, :multiply, :divide]
end
Calculator.class_eval do
OPERATIONS.each do |op|
define_method(op) do |a, b|
case op
when :add then a + b
when :subtract then a - b
when :multiply then a * b
when :divide then a / b
end
end
end
end
Common Use Cases
1. Dynamic Method Creation
class User
attr_accessor :name, :email
end
# Dynamically add validation methods
User.class_eval do
[:name, :email].each do |field|
define_method("validate_#{field}") do
!send(field).nil? && !send(field).empty?
end
end
end
2. Plugin Systems
class Plugin
def self.register(plugin_name, &block)
class_eval(&block)
end
end
Plugin.register(:logger) do
def log(message)
puts "[LOG] #{message}"
end
end
3. Configuration DSLs
class Config
def self.configure(&block)
class_eval(&block)
end
end
Config.configure do
def database_url
"postgresql://localhost/myapp"
end
def api_key
ENV['API_KEY']
end
end
Differences from instance_eval
class_eval: Executes code in the context of the class (like being inside the class definition)instance_eval: Executes code in the context of an instance of the class
class Example
def self.class_method
puts "I'm a class method"
end
def instance_method
puts "I'm an instance method"
end
end
# class_eval - can define class methods
Example.class_eval do
def self.new_class_method
puts "New class method"
end
end
# instance_eval - can define instance methods
Example.instance_eval do
def new_instance_method
puts "New instance method"
end
end
Security Considerations
⚠️ Warning: Using class_eval with user input can be dangerous:
# DANGEROUS - don't do this with user input
user_code = gets.chomp
MyClass.class_eval(user_code) # Could execute malicious code
Performance Notes
class_evalwith blocks is generally faster than with strings- The block version is also safer and more readable
- Use string evaluation only when you need to evaluate dynamic code
Real-World Example
Here’s how you might use class_eval in a practical scenario:
class ActiveRecord
def self.has_many(association_name)
class_eval do
define_method(association_name) do
# Implementation for has_many association
puts "Fetching #{association_name} for #{self.class}"
end
end
end
end
class User < ActiveRecord
has_many :posts
end
user = User.new
user.posts # => "Fetching posts for User"
class_eval is a powerful tool for meta-programming in Ruby, allowing you to dynamically modify classes at runtime. It’s commonly used in frameworks like Rails for creating DSLs and dynamic method generation.
🛠️ 2. What is instance_eval?
instance_eval?instance_eval is a method that allows you to evaluate a block of code or a string within the context of a specific object instance. It’s inherited from the BasicObject class and is available on all Ruby objects.
Basic Syntax
class MyClass
def initialize(name)
@name = name
end
end
obj = MyClass.new("Alice")
obj.instance_eval do
# code executed in the context of this specific object
puts @name # Can access instance variables
end
Key Features
1. Instance Context Execution
The code inside instance_eval is executed as if it were an instance method of the object:
class Person
def initialize(name, age)
@name = name
@age = age
end
def introduce
puts "Hi, I'm #{@name}"
end
end
person = Person.new("Bob", 30)
person.instance_eval do
puts "Name: #{@name}" # => "Name: Bob"
puts "Age: #{@age}" # => "Age: 30"
introduce # => "Hi, I'm Bob"
end
2. Access to Instance Variables
You can read and modify instance variables:
class BankAccount
def initialize(balance)
@balance = balance
end
end
account = BankAccount.new(1000)
account.instance_eval do
puts "Current balance: #{@balance}" # => "Current balance: 1000"
@balance += 500 # Modify instance variable
puts "New balance: #{@balance}" # => "New balance: 1500"
end
3. String Evaluation
You can also pass a string instead of a block:
class Example
def initialize(value)
@value = value
end
end
obj = Example.new("test")
obj.instance_eval("puts @value") # => "test"
Common Use Cases
1. Testing Private Methods
class Calculator
private
def secret_calculation(x, y)
x * y + 42
end
end
calc = Calculator.new
# Access private method in tests
result = calc.instance_eval { secret_calculation(5, 3) }
puts result # => 57
2. Dynamic Property Access
class Config
def initialize
@settings = {}
end
def set(key, value)
@settings[key] = value
end
end
config = Config.new
config.set(:api_url, "https://api.example.com")
config.set(:timeout, 30)
# Access settings dynamically
config.instance_eval do
puts @settings[:api_url] # => "https://api.example.com"
puts @settings[:timeout] # => 30
end
3. Object Inspection and Debugging
class ComplexObject
def initialize
@data = { a: 1, b: 2, c: 3 }
@metadata = { created: Time.now }
end
end
obj = ComplexObject.new
obj.instance_eval do
puts "All instance variables:"
instance_variables.each do |var|
puts "#{var}: #{instance_variable_get(var)}"
end
end
4. DSL (Domain Specific Language) Implementation
class Builder
def initialize
@result = []
end
def build(&block)
instance_eval(&block)
@result
end
end
builder = Builder.new
result = builder.build do
@result << "item1"
@result << "item2"
@result << "item3"
end
puts result # => ["item1", "item2", "item3"]
Differences from class_eval
| Feature | instance_eval | class_eval |
|---|---|---|
| Context | Object instance | Class |
| Self | Points to the object | Points to the class |
| Method Definition | Defines instance methods | Defines instance methods |
| Access | Instance variables, private methods | Class methods, constants |
class Example
def initialize
@value = "instance value"
end
@@class_var = "class variable"
end
obj = Example.new
# instance_eval - context is the object
obj.instance_eval do
puts @value # => "instance value"
puts self.class # => Example
end
# class_eval - context is the class
Example.class_eval do
puts @@class_var # => "class variable"
puts self # => Example
end
Advanced Examples
1. Method Chaining with instance_eval
class QueryBuilder
def initialize
@conditions = []
end
def where(field, value)
@conditions << "#{field} = '#{value}'"
self
end
def build
@conditions.join(" AND ")
end
end
query = QueryBuilder.new.instance_eval do
where("name", "John")
where("age", 25)
build
end
puts query # => "name = 'John' AND age = '25'"
2. Configuration Objects
class AppConfig
def initialize
@config = {}
end
def configure(&block)
instance_eval(&block)
end
def database(url)
@config[:database] = url
end
def api_key(key)
@config[:api_key] = key
end
def get_config
@config
end
end
config = AppConfig.new
config.configure do
database "postgresql://localhost/myapp"
api_key ENV['API_KEY']
end
puts config.get_config
3. Object Serialization
class Serializable
def to_hash
result = {}
instance_eval do
instance_variables.each do |var|
key = var.to_s.delete('@').to_sym
result[key] = instance_variable_get(var)
end
end
result
end
end
class User < Serializable
def initialize(name, email)
@name = name
@email = email
end
end
user = User.new("Alice", "alice@example.com")
puts user.to_hash # => {:name=>"Alice", :email=>"alice@example.com"}
Security Considerations
⚠️ Warning: Like class_eval, using instance_eval with user input can be dangerous:
# DANGEROUS - don't do this with user input
user_code = gets.chomp
obj.instance_eval(user_code) # Could execute malicious code
Performance Notes
instance_evalwith blocks is faster than with strings- The block version is safer and more readable
- Use string evaluation only when necessary for dynamic code
Real-World Usage
instance_eval is commonly used in:
- Testing frameworks (RSpec, Minitest)
- Configuration systems (Rails initializers)
- Builder patterns (ActiveRecord queries)
- DSL implementations (Rake tasks, Capistrano)
# RSpec example
describe User do
it "has a name" do
user = User.new("John")
expect(user.instance_eval { @name }).to eq("John")
end
end
instance_eval is a powerful tool for metaprogramming that allows you to execute code in the context of any object, making it invaluable for testing, debugging, and creating flexible APIs.
⚡ 3. What is eval?
eval?Ruby’s
evalmethod, which is the most powerful and potentially dangerous meta-programming tool in Ruby.
eval is a method that evaluates a string as Ruby code in the current context. It’s inherited from the Kernel module and is available everywhere in Ruby. Unlike class_eval and instance_eval, eval executes code in the current binding (the current execution context).
Basic Syntax
# Basic eval usage
eval("puts 'Hello from eval!'")
# With variables
x = 10
y = 20
result = eval("x + y")
puts result # => 30
Key Features
1. Current Context Execution
eval executes code in the current binding (local variables, methods, etc.):
def calculate(a, b, operation)
eval("#{a} #{operation} #{b}")
end
puts calculate(10, 5, "+") # => 15
puts calculate(10, 5, "*") # => 50
puts calculate(10, 5, "-") # => 5
2. Access to Local Variables
name = "Alice"
age = 25
city = "New York"
eval("puts \"#{name} is #{age} years old and lives in #{city}\"")
# => "Alice is 25 years old and lives in New York"
3. Dynamic Method Calls
class Calculator
def add(x, y)
x + y
end
def multiply(x, y)
x * y
end
end
calc = Calculator.new
method_name = "add"
args = [5, 3]
result = eval("calc.#{method_name}(#{args.join(', ')})")
puts result # => 8
Common Use Cases
1. Configuration Files
# config.rb
APP_NAME = "MyApp"
DEBUG_MODE = true
DATABASE_URL = "postgresql://localhost/myapp"
# Loading configuration
config_content = File.read('config.rb')
eval(config_content)
puts APP_NAME # => "MyApp"
puts DEBUG_MODE # => true
2. Dynamic Calculations
class MathEvaluator
def self.evaluate(expression)
eval(expression)
rescue => e
"Error: #{e.message}"
end
end
puts MathEvaluator.evaluate("2 + 3 * 4") # => 14
puts MathEvaluator.evaluate("Math.sqrt(16)") # => 4.0
puts MathEvaluator.evaluate("10 / 0") # => "Error: divided by 0"
3. Template Processing
class Template
def initialize(template)
@template = template
end
def render(binding)
eval("\"#{@template}\"", binding)
end
end
template = Template.new("Hello <%= name %>, you are <%= age %> years old!")
name = "Bob"
age = 30
result = template.render(binding)
puts result # => "Hello Bob, you are 30 years old!"
4. Code Generation
class CodeGenerator
def self.generate_method(method_name, body)
eval("
def #{method_name}
#{body}
end
")
end
end
class MyClass
CodeGenerator.generate_method(:greet, "puts 'Hello, World!'")
CodeGenerator.generate_method(:calculate, "2 + 2")
end
obj = MyClass.new
obj.greet # => "Hello, World!"
puts obj.calculate # => 4
Advanced Examples
1. Dynamic Class Creation
class DynamicClassCreator
def self.create_class(class_name, methods = {})
eval("
class #{class_name}
#{methods.map { |name, body| "def #{name}; #{body}; end" }.join("\n")}
end
")
end
end
DynamicClassCreator.create_class("Person", {
greet: "puts 'Hello!'",
age: "25"
})
person = Person.new
person.greet # => "Hello!"
puts person.age # => 25
2. Expression Parser
class ExpressionParser
def self.parse(expression, variables = {})
# Create a safe binding with variables
binding_obj = binding
variables.each do |key, value|
binding_obj.local_variable_set(key, value)
end
eval(expression, binding_obj)
end
end
result = ExpressionParser.parse("x * y + z", { x: 5, y: 3, z: 10 })
puts result # => 25
3. Configuration DSL
class AppConfig
def self.load_from_string(config_string)
eval(config_string)
end
end
config_string = "
APP_NAME = 'MyApplication'
DEBUG = true
DATABASE = {
host: 'localhost',
port: 5432,
name: 'myapp'
}
"
AppConfig.load_from_string(config_string)
puts APP_NAME # => "MyApplication"
puts DATABASE[:host] # => "localhost"
Security Risks and Dangers
⚠️ CRITICAL WARNING: eval is extremely dangerous when used with untrusted input!
Dangerous Examples:
# NEVER do this with user input!
user_input = gets.chomp
eval(user_input) # User could input: system('rm -rf /')
# Dangerous with file input
file_content = File.read('user_provided_file.rb')
eval(file_content) # Could contain malicious code
Safe Alternatives:
# Instead of eval, use safer alternatives
class SafeCalculator
def self.calculate(expression)
# Use a math library or parser instead
case expression
when /^\d+\s*\+\s*\d+$/
# Safe addition
eval(expression)
else
raise "Unsafe expression"
end
end
end
Binding and Context
1. Custom Binding
def create_binding(variables = {})
binding_obj = binding
variables.each do |key, value|
binding_obj.local_variable_set(key, value)
end
binding_obj
end
my_binding = create_binding(x: 10, y: 20)
result = eval("x + y", my_binding)
puts result # => 30
2. Different Contexts
class ContextExample
def initialize(value)
@value = value
end
def evaluate_in_context(code)
eval(code)
end
end
obj = ContextExample.new("test")
result = obj.evaluate_in_context("@value")
puts result # => "test"
Performance Considerations
evalis slower than direct code execution- The string must be parsed every time
- No compile-time optimization
- Use sparingly and only when necessary
Best Practices
1. Avoid When Possible
# Bad - using eval
method_name = "calculate"
eval("result = #{method_name}(5, 3)")
# Good - using send
result = send(method_name, 5, 3)
2. Whitelist Allowed Operations
class SafeEvaluator
ALLOWED_OPERATIONS = %w[+ - * /]
def self.evaluate(expression)
operation = expression.match(/(\d+)\s*([+\-*/])\s*(\d+)/)
return "Invalid expression" unless operation
op = operation[2]
return "Operation not allowed" unless ALLOWED_OPERATIONS.include?(op)
eval(expression)
end
end
3. Use Sandboxing
class SandboxedEval
def self.safe_eval(code, timeout: 1)
Timeout::timeout(timeout) do
eval(code)
end
rescue Timeout::Error
"Execution timed out"
rescue => e
"Error: #{e.message}"
end
end
Real-World Usage
eval is used in:
- Template engines (ERB, Haml)
- Configuration systems (Rails initializers)
- Code generators (Rails generators)
- REPLs (Interactive Ruby shells)
# ERB template example
require 'erb'
template = ERB.new("Hello <%= name %>!")
name = "World"
result = template.result(binding)
puts result # => "Hello World!"
Summary
eval is the most powerful metaprogramming tool in Ruby, but also the most dangerous. Use it only when:
- You have complete control over the input
- No safer alternative exists
- You understand the security implications
- You implement proper validation and sandboxing
For most use cases, prefer safer alternatives like send, method_missing, or dedicated parsing libraries.
✍️ 4. What is define_method?
define_method?Ruby’s
define_method, which is a powerful meta-programming tool for dynamically creating methods at runtime.
define_method is a method that allows you to dynamically define instance methods on a class or module. It’s inherited from the Module class and is a key tool for meta-programming in Ruby.
Basic Syntax
class MyClass
define_method :dynamic_method do |*args|
puts "Hello from dynamic method with args: #{args}"
end
end
obj = MyClass.new
obj.dynamic_method("test", 123) # => "Hello from dynamic method with args: [\"test\", 123]"
Key Features
1. Dynamic Method Creation
class Calculator
# Define methods dynamically based on operations
[:add, :subtract, :multiply, :divide].each do |operation|
define_method operation do |a, b|
case operation
when :add then a + b
when :subtract then a - b
when :multiply then a * b
when :divide then a / b
end
end
end
end
calc = Calculator.new
puts calc.add(5, 3) # => 8
puts calc.multiply(4, 2) # => 8
puts calc.divide(10, 2) # => 5
2. Access to Instance Variables
class Person
def initialize(name, age)
@name = name
@age = age
end
# Create getter methods dynamically
[:name, :age].each do |attribute|
define_method attribute do
instance_variable_get("@#{attribute}")
end
end
end
person = Person.new("Alice", 25)
puts person.name # => "Alice"
puts person.age # => 25
3. Method with Parameters
class DynamicAPI
define_method :api_call do |endpoint, params = {}|
puts "Calling #{endpoint} with params: #{params}"
# Simulate API call
"Response from #{endpoint}"
end
end
api = DynamicAPI.new
api.api_call("/users", { id: 1 }) # => "Calling /users with params: {:id=>1}"
Common Use Cases
1. Attribute Accessors
class Model
def self.attr_accessor(*attributes)
attributes.each do |attribute|
# Define getter
define_method attribute do
instance_variable_get("@#{attribute}")
end
# Define setter
define_method "#{attribute}=" do |value|
instance_variable_set("@#{attribute}", value)
end
end
end
attr_accessor :name, :email, :age
end
user = Model.new
user.name = "John"
user.email = "john@example.com"
puts user.name # => "John"
puts user.email # => "john@example.com"
2. Validation Methods
class User
def initialize(attributes = {})
attributes.each do |key, value|
instance_variable_set("@#{key}", value)
end
end
# Create validation methods dynamically
[:name, :email, :age].each do |field|
define_method "validate_#{field}" do
value = instance_variable_get("@#{field}")
case field
when :name
!value.nil? && !value.empty?
when :email
value =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
when :age
value.is_a?(Integer) && value > 0 && value < 150
end
end
end
end
user = User.new(name: "Alice", email: "alice@example.com", age: 25)
puts user.validate_name # => true
puts user.validate_email # => true
puts user.validate_age # => true
3. API Method Generation
class APIClient
def initialize(base_url)
@base_url = base_url
end
# Generate API methods for different resources
[:users, :posts, :comments].each do |resource|
define_method "get_#{resource}" do |id = nil|
endpoint = id ? "#{resource}/#{id}" : resource
puts "GET #{@base_url}/#{endpoint}"
# Actual HTTP request would go here
end
define_method "create_#{resource.singularize}" do |data|
puts "POST #{@base_url}/#{resource}"
puts "Data: #{data}"
# Actual HTTP request would go here
end
end
end
api = APIClient.new("https://api.example.com")
api.get_users # => "GET https://api.example.com/users"
api.get_users(123) # => "GET https://api.example.com/users/123"
api.create_user(name: "John") # => "POST https://api.example.com/users"
4. Database Query Methods
class ActiveRecord
def self.has_many(association_name)
define_method association_name do
puts "Fetching #{association_name} for #{self.class}"
# Actual database query would go here
[]
end
end
def self.belongs_to(association_name)
define_method association_name do
puts "Fetching #{association_name} for #{self.class}"
# Actual database query would go here
nil
end
end
end
class User < ActiveRecord
has_many :posts
belongs_to :company
end
user = User.new
user.posts # => "Fetching posts for User"
user.company # => "Fetching company for User"
Advanced Examples
1. Method with Dynamic Logic
class DynamicCalculator
def self.create_operation(operation_name, &block)
define_method operation_name do |*args|
instance_eval(&block)
end
end
create_operation :custom_add do
args = method(__method__).parameters.map { |_, name| local_variable_get(name) }
args.inject(:+)
end
create_operation :power do
base, exponent = method(__method__).parameters.map { |_, name| local_variable_get(name) }
base ** exponent
end
end
calc = DynamicCalculator.new
puts calc.custom_add(1, 2, 3, 4) # => 10
puts calc.power(2, 3) # => 8
2. Conditional Method Definition
class FeatureToggle
def self.define_feature_method(feature_name, enabled = true)
if enabled
define_method feature_name do
puts "Feature #{feature_name} is enabled"
# Feature implementation
end
else
define_method feature_name do
puts "Feature #{feature_name} is disabled"
# Fallback or no-op
end
end
end
define_feature_method :new_ui, true
define_feature_method :beta_feature, false
end
app = FeatureToggle.new
app.new_ui # => "Feature new_ui is enabled"
app.beta_feature # => "Feature beta_feature is disabled"
3. Method with Different Signatures
class FlexibleAPI
def self.define_flexible_method(method_name)
define_method method_name do |*args, **kwargs, &block|
puts "Method: #{method_name}"
puts "Arguments: #{args}"
puts "Keyword arguments: #{kwargs}"
puts "Block given: #{block_given?}"
# Process based on arguments
if block_given?
block.call(args.first)
elsif !kwargs.empty?
kwargs.values.first
else
args.first
end
end
end
define_flexible_method :process
end
api = FlexibleAPI.new
api.process("hello") # => "hello"
api.process(data: "world") # => "world"
api.process("test") { |x| x.upcase } # => "TEST"
4. Method Aliasing
class MethodAliaser
def self.create_aliases(base_method, *aliases)
aliases.each do |alias_name|
define_method alias_name do |*args, &block|
send(base_method, *args, &block)
end
end
end
def original_method
puts "Original method called"
end
create_aliases :original_method, :alias1, :alias2, :alternative_name
end
obj = MethodAliaser.new
obj.alias1 # => "Original method called"
obj.alternative_name # => "Original method called"
Performance Considerations
1. Method Definition Timing
class PerformanceExample
# Methods defined at class definition time (faster)
define_method :static_method do
puts "Static method"
end
def self.create_dynamic_methods
# Methods defined at runtime (slower)
1000.times do |i|
define_method "dynamic_method_#{i}" do
puts "Dynamic method #{i}"
end
end
end
end
2. Memory Usage
class MemoryEfficient
# More memory efficient - methods defined once
METHODS = [:method1, :method2, :method3]
METHODS.each do |method_name|
define_method method_name do
puts "Called #{method_name}"
end
end
end
Best Practices
1. Use Meaningful Names
# Good
define_method :calculate_total do
# implementation
end
# Bad
define_method :m1 do
# implementation
end
2. Handle Errors Gracefully
class SafeMethodDefiner
def self.define_safe_method(method_name, &block)
define_method method_name do |*args|
begin
instance_eval(&block)
rescue => e
puts "Error in #{method_name}: #{e.message}"
nil
end
end
end
end
3. Document Dynamic Methods
class DocumentedClass
# Dynamically creates getter methods for attributes
# @param attributes [Array<Symbol>] list of attribute names
def self.create_getters(*attributes)
attributes.each do |attr|
define_method attr do
instance_variable_get("@#{attr}")
end
end
end
create_getters :name, :email
end
Real-World Usage
define_method is commonly used in:
- Rails ActiveRecord (associations, validations)
- RSpec (dynamic test methods)
- Configuration systems (dynamic setters/getters)
- API clients (dynamic endpoint methods)
- ORM frameworks (dynamic query methods)
# Rails-like example
class ActiveRecord
def self.validates_presence_of(*attributes)
attributes.each do |attribute|
define_method "validate_#{attribute}_presence" do
value = send(attribute)
value.nil? || value.to_s.empty?
end
end
end
end
class User < ActiveRecord
validates_presence_of :name, :email
end
define_method is a powerful tool for creating flexible, dynamic APIs and reducing code duplication through meta-programming.
🌀 5. What is method_missing?
method_missing?Ruby’s
method_missing, which is a powerful meta-programming tool that allows you to handle calls to undefined methods dynamically.
method_missing is a special method in Ruby that gets called automatically when an object receives a message (method call) for a method that doesn’t exist. It’s inherited from the BasicObject class and is a key component of Ruby’s dynamic nature.
Basic Syntax
class MyClass
def method_missing(method_name, *args, &block)
puts "Method '#{method_name}' called with args: #{args}"
# Handle the missing method call
end
end
obj = MyClass.new
obj.undefined_method("hello", 123) # => "Method 'undefined_method' called with args: [\"hello\", 123]"
Key Features
1. Automatic Invocation
class DynamicHandler
def method_missing(method_name, *args, &block)
puts "Trying to call: #{method_name}"
puts "Arguments: #{args}"
puts "Block given: #{block_given?}"
# Return a default value or handle the call
"Handled by method_missing"
end
end
obj = DynamicHandler.new
result = obj.some_random_method("arg1", "arg2") { puts "block" }
puts result
# Output:
# Trying to call: some_random_method
# Arguments: ["arg1", "arg2"]
# Block given: true
# Handled by method_missing
2. Method Name and Arguments
class FlexibleAPI
def method_missing(method_name, *args, **kwargs, &block)
puts "Method: #{method_name}"
puts "Arguments: #{args}"
puts "Keyword arguments: #{kwargs}"
# Handle different method patterns
case method_name.to_s
when /^get_(.+)$/
"Getting #{$1}"
when /^set_(.+)$/
"Setting #{$1} to #{args.first}"
else
"Unknown method: #{method_name}"
end
end
end
api = FlexibleAPI.new
puts api.get_user_info # => "Getting user_info"
puts api.set_name("Alice") # => "Setting name to Alice"
puts api.random_method # => "Unknown method: random_method"
Common Use Cases
1. Dynamic Property Access
class DynamicProperties
def initialize
@data = {}
end
def method_missing(method_name, *args)
method_str = method_name.to_s
if method_str.end_with?('=')
# Setter method
property_name = method_str.chomp('=')
@data[property_name] = args.first
else
# Getter method
@data[method_str]
end
end
end
obj = DynamicProperties.new
obj.name = "Alice"
obj.age = 25
obj.city = "New York"
puts obj.name # => "Alice"
puts obj.age # => 25
puts obj.city # => "New York"
2. API Method Generation
class APIClient
def initialize(base_url)
@base_url = base_url
end
def method_missing(method_name, *args)
method_str = method_name.to_s
case method_str
when /^get_(.+)$/
resource = $1
puts "GET #{@base_url}/#{resource}"
# Actual HTTP GET request
when /^post_(.+)$/
resource = $1
data = args.first || {}
puts "POST #{@base_url}/#{resource}"
puts "Data: #{data}"
# Actual HTTP POST request
when /^put_(.+)$/
resource = $1
data = args.first || {}
puts "PUT #{@base_url}/#{resource}"
puts "Data: #{data}"
# Actual HTTP PUT request
when /^delete_(.+)$/
resource = $1
puts "DELETE #{@base_url}/#{resource}"
# Actual HTTP DELETE request
else
super
end
end
end
api = APIClient.new("https://api.example.com")
api.get_users # => "GET https://api.example.com/users"
api.post_user(name: "John") # => "POST https://api.example.com/user"
api.put_user(1, name: "Jane") # => "PUT https://api.example.com/user"
api.delete_user(1) # => "DELETE https://api.example.com/user"
3. Configuration DSL
class Configuration
def initialize
@config = {}
end
def method_missing(method_name, *args)
method_str = method_name.to_s
if method_str.end_with?('=')
# Setter
key = method_str.chomp('=')
@config[key] = args.first
else
# Getter
@config[method_str]
end
end
def to_hash
@config
end
end
config = Configuration.new
config.database_url = "postgresql://localhost/myapp"
config.api_key = ENV['API_KEY']
config.debug_mode = true
puts config.database_url # => "postgresql://localhost/myapp"
puts config.to_hash # => {"database_url"=>"postgresql://localhost/myapp", ...}
4. Builder Pattern
class HTMLBuilder
def initialize
@html = []
end
def method_missing(tag_name, *args, &block)
content = args.first || ""
attributes = args.last.is_a?(Hash) ? args.last : {}
if block_given?
@html << "<#{tag_name}#{format_attributes(attributes)}>"
@html << yield
@html << "</#{tag_name}>"
else
@html << "<#{tag_name}#{format_attributes(attributes)}>#{content}</#{tag_name}>"
end
self
end
private
def format_attributes(attributes)
return "" if attributes.empty?
" " + attributes.map { |k, v| "#{k}=\"#{v}\"" }.join(" ")
end
def to_s
@html.join("\n")
end
end
builder = HTMLBuilder.new
html = builder.html do
builder.head do
builder.title("My Page")
end
builder.body(class: "main") do
builder.h1("Hello World")
builder.p("This is a paragraph", class: "intro")
end
end
puts html
# Output:
# <html>
# <head>
# <title>My Page</title>
# </head>
# <body class="main">
# <h1>Hello World</h1>
# <p class="intro">This is a paragraph</p>
# </body>
# </html>
Advanced Examples
1. Method Caching with respond_to_missing?
class CachedMethodHandler
def initialize
@cache = {}
end
def method_missing(method_name, *args)
method_str = method_name.to_s
# Check if we should handle this method
if method_str.start_with?('cached_')
cache_key = "#{method_str}_#{args.hash}"
if @cache.key?(cache_key)
puts "Returning cached result for #{method_name}"
@cache[cache_key]
else
puts "Computing result for #{method_name}"
result = compute_result(method_str, args)
@cache[cache_key] = result
result
end
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?('cached_') || super
end
private
def compute_result(method_name, args)
# Simulate expensive computation
sleep(1)
"Result for #{method_name} with #{args}"
end
end
handler = CachedMethodHandler.new
puts handler.cached_expensive_calculation(1, 2, 3) # Slow
puts handler.cached_expensive_calculation(1, 2, 3) # Fast (cached)
puts handler.respond_to?(:cached_expensive_calculation) # => true
2. Dynamic Delegation
class Delegator
def initialize(target)
@target = target
end
def method_missing(method_name, *args, &block)
if @target.respond_to?(method_name)
@target.send(method_name, *args, &block)
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
@target.respond_to?(method_name, include_private) || super
end
end
class User
def initialize(name, email)
@name = name
@email = email
end
def display_info
"Name: #{@name}, Email: #{@email}"
end
end
user = User.new("Alice", "alice@example.com")
delegator = Delegator.new(user)
puts delegator.display_info # => "Name: Alice, Email: alice@example.com"
3. Method Chaining with method_missing
class QueryBuilder
def initialize
@conditions = []
@order_by = nil
@limit = nil
end
def method_missing(method_name, *args)
method_str = method_name.to_s
case method_str
when /^where_(.+)$/
field = $1
value = args.first
@conditions << "#{field} = '#{value}'"
self
when /^order_by_(.+)$/
field = $1
direction = args.first || 'ASC'
@order_by = "#{field} #{direction}"
self
when /^limit$/
@limit = args.first
self
when /^execute$/
build_query
else
super
end
end
private
def build_query
query = "SELECT * FROM table"
query += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
query += " ORDER BY #{@order_by}" if @order_by
query += " LIMIT #{@limit}" if @limit
query
end
end
query = QueryBuilder.new
result = query.where_name("John")
.where_age(25)
.order_by_created_at("DESC")
.limit(10)
.execute
puts result
# => "SELECT * FROM table WHERE name = 'John' AND age = '25' ORDER BY created_at DESC LIMIT 10"
4. Event Handling
class EventHandler
def initialize
@handlers = {}
end
def method_missing(event_name, *args)
method_str = event_name.to_s
if method_str.end_with?('=')
# Register event handler
handler_name = method_str.chomp('=')
@handlers[handler_name] = args.first
else
# Trigger event
handler = @handlers[method_str]
if handler
handler.call(*args)
else
puts "No handler registered for event: #{method_str}"
end
end
end
end
handler = EventHandler.new
# Register event handlers
handler.on_click = ->(x, y) { puts "Clicked at (#{x}, #{y})" }
handler.on_hover = ->(element) { puts "Hovered over #{element}" }
# Trigger events
handler.on_click(100, 200) # => "Clicked at (100, 200)"
handler.on_hover("button") # => "Hovered over button"
handler.on_keypress # => "No handler registered for event: on_keypress"
Important Considerations
1. respond_to_missing?
Always implement respond_to_missing? when using method_missing:
class ProperHandler
def method_missing(method_name, *args)
if method_name.to_s.start_with?('dynamic_')
"Handled: #{method_name}"
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?('dynamic_') || super
end
end
obj = ProperHandler.new
puts obj.respond_to?(:dynamic_method) # => true
puts obj.respond_to?(:real_method) # => false
2. Performance Impact
class PerformanceExample
def method_missing(method_name, *args)
# This gets called for EVERY undefined method
# Can be slow if called frequently
puts "Handling #{method_name}"
end
end
# Better approach: Define methods when first called
class BetterExample
def method_missing(method_name, *args)
if method_name.to_s.start_with?('dynamic_')
# Define the method for future calls
self.class.define_method(method_name) do |*method_args|
"Handled: #{method_name} with #{method_args}"
end
# Call the newly defined method
send(method_name, *args)
else
super
end
end
end
3. Debugging
class DebuggableHandler
def method_missing(method_name, *args, &block)
puts "DEBUG: method_missing called for #{method_name}"
puts "DEBUG: arguments: #{args}"
puts "DEBUG: block given: #{block_given?}"
# Your handling logic here
super
end
end
Real-World Usage
method_missing is commonly used in:
- Rails ActiveRecord (dynamic finders)
- RSpec (dynamic matchers)
- Sinatra (dynamic route handlers)
- Configuration systems (dynamic setters/getters)
- API clients (dynamic endpoint methods)
# Rails-like example
class ActiveRecord
def method_missing(method_name, *args)
method_str = method_name.to_s
if method_str.start_with?('find_by_')
field = method_str.sub('find_by_', '')
value = args.first
puts "Finding record where #{field} = #{value}"
# Actual database query
else
super
end
end
end
class User < ActiveRecord
end
User.find_by_name("Alice") # => "Finding record where name = Alice"
User.find_by_email("alice@example.com") # => "Finding record where email = alice@example.com"
method_missing is a powerful tool for creating flexible, dynamic APIs, but should be used carefully with proper implementation of respond_to_missing? and consideration for performance.
🏷️ Part 2: Other Metaprogramming Tools
Ruby provides many more hooks for dynamic behavior:
🔗 1. define_singleton_method
Creates a method on a single object, similar to instance_eval + def.
str = "hello"
def str.shout; upcase + '!'; end
# vs.
str.define_singleton_method(:whisper) { downcase + '...' }
📦 2. module_eval / module_exec
Like class_eval but in a module’s context, useful to mix in behavior.
module Mixin; end
Mixin.module_eval do
def helper; "I'm mixed in"; end
end
🧩 3. send / public_send
Invoke methods by name, bypassing visibility with send, or respecting it with public_send.
obj.send(:private_method)
obj.public_send(:public_method)
Both send and public_send in Ruby allow you to dynamically call methods on an object by name (as a symbol or string).
But the difference lies in method visibility (public, private, protected).
Key Difference
| Method | Can call private/protected methods? | Example Usage |
|---|---|---|
send | ✅ Yes | "hello".send(:upcase) |
public_send | ❌ No (only public methods) | "hello".public_send(:upcase) |
Example
class MyClass
def public_method
"I'm public"
end
private
def secret_method
"I'm private"
end
end
obj = MyClass.new
puts obj.send(:public_method) # ✅ Works: "I'm public"
puts obj.public_send(:public_method) # ✅ Works: "I'm public"
puts obj.send(:secret_method) # ✅ Works (can access private method)
puts obj.public_send(:secret_method) # ❌ Raises NoMethodError
? Why this matters
sendis very powerful (and dangerous) because it ignores method visibility. It’s often used in metaprogramming (e.g., ActiveRecord uses it internally).public_sendis safer because it respects encapsulation. Use this when you don’t want to accidentally call private methods.
Best Practice
- Use
public_sendwhenever possible (especially if the method name comes from user input) to avoid security issues. - Use
sendonly when you explicitly need access to private methods (e.g., in testing or metaprogramming).
🏷️ 4. Constant Manipulation (const_get, const_set)
Dynamically read or write constants on classes or modules.
Object.const_set('DynamicClass', Class.new)
klass = Object.const_get('DynamicClass')
🛠️ 5. Alias & Removal (alias_method, undef_method, remove_method)
Alias existing methods or remove definitions at runtime.
def foo; :bar; end
alias_method :baz, :foo
undef_method :foo
📥 6. Instance Variable Access (instance_variable_get, instance_variable_set)
Read or write any object’s internal state.
obj.instance_variable_set(:@data, 123)
puts obj.instance_variable_get(:@data) # => 123
🔮 7. Eigenclass & class << obj
Open the singleton class to define methods or mixins.
class << obj
def special; 'only me'; end
end
📡 8. autoload / require_relative
Delay loading of modules until first reference.
autoload :Parser, 'parser'
📐 9. respond_to_missing?
Complement method_missing to accurately report capabilities.
def respond_to_missing?(m, include_private=false)
@target.respond_to?(m) || super
end
🔀 10. Refinements
Scoped monkey-patching without global impact.
module StringExtensions
refine String do
def shout; upcase; end
end
end
using StringExtensions
puts 'hi'.shout # => HI
✅ Conclusion
Ruby’s metaprogramming toolbox is vast—from the core five methods in Part 1 to the advanced techniques in Part 2. By mastering these, you can write highly dynamic, DRY, and expressive code, but always balance power with clarity and maintainability.
Happy Ruby coding! 🚀
🔍 1. Why Use class_eval and instance_eval?
Ruby classes and objects are open, meaning you can modify them at runtime.
class_evallets you inject instance methods into a class after it’s defined.instance_evallets you add singleton methods or access private state on a single object.
✨ Dynamic Method Generation
When you don’t know ahead of time what methods you’ll need—say you’re reading an external schema or configuration—you can’t hand‑write every method. Metaprogramming with class_eval/instance_eval generates them on the fly.
🔧 Building DSLs
Internal DSLs (e.g. Rails’ routing or configuration blocks) rely on evaluating blocks in the right context:
# routes.rb
Rails.application.routes.draw do
resources :users do
resources :posts
end
end
Here, instance_eval on the routing object makes resources available inside the block.
If you know all your methods at design time, define them normally. Lean on metaprogramming when you need flexibility, DRY generation, or want to craft a clean DSL.
🛠️ 2. When to Use define_method?
define_method is Ruby’s block‑based way to dynamically add methods in a class or module—safer than eval and can close over local variables:
class Serializer
%i[name age email].each do |attr|
define_method("serialize_#{attr}") do |user|
user.public_send(attr).to_s
end
end
end
- Ideal for generating many similar methods (attribute serializers, dynamic finders).
- Keeps code DRY without string interpolation.
📦 3. Ruby’s *arg and **arg Operators
⭐ Single Splat: *args
- Definition: Packs extra positional args into an array, or unpacks an array into individual arguments.
def foo(a, *others) p a # first arg p others # rest as an array end foo(1, 2, 3) # a=1, others=[2,3] foo(*[4,5,6]) # same as foo(4,5,6)
✨ Double Splat: **kwargs
- Definition: Packs extra keyword args into a hash, or unpacks a hash into keyword arguments.
def bar(x:, **opts) p x # required keyword p opts # other keywords in a hash end bar(x: 10, y: 20, z: 30) # opts={y:20, z:30} bar(**{x:1, y:2}) # same as bar(x:1, y:2)
📚 4. What Is a DSL? Main Ruby DSLs in Rails
A DSL (Domain‑Specific Language) is an internal API that reads like its own language, tailored to a task. Rails ships with many:
🚦 Routing DSL
Rails.application.routes.draw do
resources :users
end
🏗️ ActiveRecord Query DSL
User.where(active: true)
.order(created_at: :desc)
.limit(10)
🗄️ Migrations DSL
class CreateProducts < ActiveRecord::Migration[8.0]
def change
create_table :products do |t|
t.string :name
t.decimal :price
end
end
end
💬 Validation DSL
class User < ApplicationRecord
validates :email, presence: true, uniqueness: true
end
🔄 Callback DSL
class Order < ApplicationRecord
before_save :calculate_total
after_commit :send_confirmation_email
end
✔️ These meta-programming techniques and splat operators power Ruby’s flexibility—and underpin the expressive DSLs that make Rails code so readable.
Enjoy Ruby! 🚀






