Introduction
Ruby provides a family of *_eval and *_exec methods that let you evaluate code in the context of a different object — changing what self refers to inside a block. These are powerful metaprogramming tools used in DSLs, configuration APIs, and testing frameworks.
instance_eval: Evaluate in Object Context
instance_eval evaluates a block (or string) in the context of the receiver. Inside the block, self is the receiver object, giving you access to its private methods and instance variables:
class Counter
DEFAULT = 0
attr_reader :counter
def initialize(start = DEFAULT)
@counter = start
end
def inc
@counter += 1
end
end
c = Counter.new(5)
# Access private state directly
c.instance_eval do
puts @counter # => 5 (direct access to instance variable)
puts self # => #<Counter:0x...>
end
# Define a singleton method
c.instance_eval do
def reset
@counter = 0
end
end
c.reset
puts c.counter # => 0
String Form (Avoid in Production)
instance_eval also accepts a string, but this is a security risk — never use it with user-supplied input:
# DANGEROUS — code injection risk
module Reset
def self.reset_var(object, name)
object.instance_eval("@#{name} = DEFAULT") # injection risk!
end
end
instance_exec: Like instance_eval but Accepts Arguments
instance_exec is identical to instance_eval but allows you to pass arguments to the block. This is crucial when you need to pass local variables into the block without them being shadowed by the new self context:
module Reset
def self.reset_var(object, name)
# Pass the variable name as an argument to the block
object.instance_exec("@#{name}".to_sym) do |var|
const = self.class.const_get(:DEFAULT)
instance_variable_set(var, const)
end
end
end
c = Counter.new(10)
puts c.counter # => 10
Reset.reset_var(c, "counter")
puts c.counter # => 0 (reset to DEFAULT)
The key difference: with instance_eval, you can’t easily pass arguments to the block. With instance_exec, you can:
multiplier = 3
# instance_eval — can't easily use 'multiplier' inside
obj.instance_eval { @value * multiplier } # works but captures outer scope
# instance_exec — explicitly pass arguments
obj.instance_exec(multiplier) { |m| @value * m } # cleaner, explicit
class_eval / module_eval: Open a Class Dynamically
class_eval (aliased as module_eval) evaluates a block in the context of a class or module. Inside the block, self is the class, so you can define instance methods, class methods, and access class-level state:
class Dog; end
# Add instance methods to Dog dynamically
Dog.class_eval do
def speak
"Woof!"
end
def fetch(item)
"#{name} fetches the #{item}!"
end
attr_accessor :name
end
rex = Dog.new
rex.name = "Rex"
puts rex.speak # => "Woof!"
puts rex.fetch("ball") # => "Rex fetches the ball!"
This is equivalent to reopening the class, but can be done dynamically:
# These are equivalent:
class Dog
def speak; "Woof!"; end
end
Dog.class_eval do
def speak; "Woof!"; end
end
Defining Methods with class_eval + String
# Generate methods from a list
[:name, :email, :age].each do |attr|
User.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def #{attr}
@#{attr}
end
def #{attr}=(value)
@#{attr} = value
end
RUBY
end
Practical Use Cases
DSL Configuration
instance_eval is the backbone of many Ruby DSLs — it lets users write configuration code that looks like it belongs to the object:
class ServerConfig
attr_reader :host, :port, :ssl, :timeout
def initialize
@host = "localhost"
@port = 3000
@ssl = false
@timeout = 30
end
def configure(&block)
instance_eval(&block)
self
end
private
def host(value); @host = value; end
def port(value); @port = value; end
def ssl(value); @ssl = value; end
def timeout(value); @timeout = value; end
end
config = ServerConfig.new.configure do
host "api.example.com"
port 443
ssl true
timeout 60
end
puts config.host # => "api.example.com"
puts config.port # => 443
puts config.ssl # => true
RSpec-style Test DSL
RSpec uses instance_eval extensively:
class TestSuite
def initialize(description, &block)
@description = description
@tests = []
instance_eval(&block) # run the block in this object's context
end
def it(description, &block)
@tests << { description: description, block: block }
end
def run
puts "#{@description}:"
@tests.each do |test|
begin
instance_eval(&test[:block])
puts " ✓ #{test[:description]}"
rescue => e
puts " ✗ #{test[:description]}: #{e.message}"
end
end
end
end
TestSuite.new("Calculator") do
it "adds two numbers" do
raise "Expected 5" unless 2 + 3 == 5
end
it "subtracts numbers" do
raise "Expected 1" unless 3 - 2 == 1
end
end.run
# => Calculator:
# => ✓ adds two numbers
# => ✓ subtracts numbers
Rake-style Task Definition
class TaskRunner
def initialize(&block)
@tasks = {}
instance_eval(&block)
end
def task(name, depends_on: [], &block)
@tasks[name] = { depends_on: depends_on, block: block }
end
def run(name)
t = @tasks[name]
t[:depends_on].each { |dep| run(dep) }
t[:block].call
end
end
runner = TaskRunner.new do
task(:compile) { puts "Compiling..." }
task(:test, depends_on: [:compile]) { puts "Testing..." }
task(:deploy, depends_on: [:test]) { puts "Deploying..." }
end
runner.run(:deploy)
# => Compiling...
# => Testing...
# => Deploying...
Adding Methods to Multiple Classes
[String, Array, Hash].each do |klass|
klass.class_eval do
def blank?
respond_to?(:empty?) ? empty? : !self
end
end
end
"".blank? # => true
[].blank? # => true
{}.blank? # => true
"hi".blank? # => false
Comparison Table
| Method | Receiver | Arguments to block | Use case |
|---|---|---|---|
obj.instance_eval |
obj |
No | Access/modify object internals, DSLs |
obj.instance_exec(args) |
obj |
Yes | Same as above, but with arguments |
Klass.class_eval |
Klass |
No | Add methods to a class dynamically |
Klass.module_eval |
Klass |
No | Alias for class_eval |
Security Warning
Never use the string form of instance_eval or class_eval with user-supplied input:
# DANGEROUS
user_input = "system('rm -rf /')"
obj.instance_eval(user_input) # arbitrary code execution!
# SAFE — always use block form
obj.instance_eval { do_something_safe }
Resources
- Ruby Docs: BasicObject#instance_eval
- Ruby Docs: BasicObject#instance_exec
- Ruby Docs: Module#class_eval
- Effective Ruby — Item 31: Prefer instance_exec over instance_eval
- Metaprogramming Ruby — Paolo Perrotta
Comments