Overview
The instance_eval
method executes code within the context of a specific object instance, changing the value of self
during execution. Ruby provides instance_eval
as a method available on all objects, allowing dynamic evaluation of strings or blocks within that object's scope.
When Ruby calls instance_eval
, the receiver becomes self
for the duration of the block or string evaluation. This grants access to the receiver's instance variables, private methods, and allows method definitions to be added to the receiver's singleton class.
class Person
def initialize(name)
@name = name
end
private
def secret
"My secret is #{@name}"
end
end
person = Person.new("Alice")
# Access private method and instance variable
person.instance_eval { secret }
# => "My secret is Alice"
person.instance_eval { @name }
# => "Alice"
Ruby distinguishes instance_eval
from class_eval
(also known as module_eval
) by the context it creates. While class_eval
executes code in the context of a class or module definition, instance_eval
executes code in the context of a specific instance.
The method accepts either a string containing Ruby code or a block. When passed a string, Ruby compiles and executes the code within the receiver's context. Block form provides better performance and cleaner syntax for most use cases.
obj = Object.new
# String form
obj.instance_eval("def hello; 'Hello from singleton'; end")
obj.hello
# => "Hello from singleton"
# Block form (preferred)
obj.instance_eval do
def goodbye
"Goodbye from singleton"
end
end
obj.goodbye
# => "Goodbye from singleton"
Basic Usage
The instance_eval
method changes the execution context to the receiver object, making its instance variables and private methods accessible within the evaluation block. This context switching forms the foundation for metaprogramming patterns and dynamic behavior modification.
class Calculator
def initialize
@total = 0
end
private
def add_internal(value)
@total += value
end
end
calc = Calculator.new
# Access and modify private state
calc.instance_eval do
add_internal(10)
add_internal(5)
@total
end
# => 15
Ruby evaluates the block or string with the receiver as self
, enabling direct access to instance variables without accessor methods. This access bypasses normal encapsulation boundaries, making instance_eval
both powerful and potentially dangerous.
Method definitions within instance_eval
create singleton methods on the receiver object. These methods exist only on that specific instance, not on the class or other instances of the same class.
string = "example"
string.instance_eval do
def reverse_upcase
reverse.upcase
end
end
string.reverse_upcase
# => "ELPMAXE"
# Method only exists on this specific string instance
"other".reverse_upcase
# NoMethodError: undefined method `reverse_upcase' for "other":String
The block passed to instance_eval
can access variables from the surrounding scope (closure variables) while also having access to the receiver's internal state. This creates a bridge between external context and internal object state.
class DataStore
def initialize
@data = {}
end
private
def store(key, value)
@data[key] = value
end
def retrieve(key)
@data[key]
end
end
store = DataStore.new
external_key = :user_id
external_value = 12345
store.instance_eval do
store(external_key, external_value)
retrieve(:user_id)
end
# => 12345
String evaluation with instance_eval
accepts optional filename and line number parameters for better error reporting and debugging. These parameters help Ruby generate more meaningful stack traces when errors occur during evaluation.
obj = Object.new
obj.instance_eval(<<-CODE, "dynamic_code.rb", 1)
def dynamic_method
raise "Error in dynamic code"
end
CODE
obj.dynamic_method
# RuntimeError: Error in dynamic code
# from dynamic_code.rb:3:in `dynamic_method'
Advanced Usage
Complex metaprogramming scenarios often combine instance_eval
with other Ruby features to create sophisticated dynamic behavior. Method chaining with instance_eval
enables fluent interfaces and domain-specific languages by returning appropriate objects from evaluation contexts.
class ConfigBuilder
def initialize
@config = {}
end
def database(&block)
@config[:database] = DatabaseConfig.new.instance_eval(&block)
self
end
def cache(&block)
@config[:cache] = CacheConfig.new.instance_eval(&block)
self
end
def build
@config
end
end
class DatabaseConfig
def initialize
@settings = {}
end
def host(value)
@settings[:host] = value
@settings
end
def port(value)
@settings[:port] = value
@settings
end
end
class CacheConfig
def initialize
@settings = {}
end
def type(value)
@settings[:type] = value
@settings
end
def ttl(value)
@settings[:ttl] = value
@settings
end
end
config = ConfigBuilder.new
.database { host("localhost").port(5432) }
.cache { type("redis").ttl(3600) }
.build
# => {:database=>{:host=>"localhost", :port=>5432}, :cache=>{:type=>"redis", :ttl=>3600}}
Proxy objects frequently use instance_eval
to delegate behavior while maintaining the appearance of direct object interaction. This pattern supports method forwarding with context preservation.
class LoggingProxy
def initialize(target)
@target = target
@log = []
end
def method_missing(method_name, *args, &block)
@log << { method: method_name, args: args, timestamp: Time.now }
if block
@target.instance_eval do
send(method_name, *args, &block)
end
else
@target.send(method_name, *args)
end
end
def execution_log
@log
end
end
class Worker
def initialize
@completed_tasks = 0
end
def process_task(task_name)
@completed_tasks += 1
"Processed #{task_name}"
end
private
def task_count
@completed_tasks
end
end
worker = Worker.new
proxy = LoggingProxy.new(worker)
result = proxy.process_task("data_sync")
# => "Processed data_sync"
# Access private method through proxy
proxy.instance_eval { task_count }
# => 1
proxy.execution_log
# => [{:method=>:process_task, :args=>["data_sync"], :timestamp=>...}]
Template systems and code generation tools combine instance_eval
with string interpolation to create dynamic code structures. This approach supports runtime code generation based on configuration or data.
class MethodGenerator
def self.create_accessor_methods(klass, attributes)
attributes.each do |attr|
klass.instance_eval <<-CODE
def #{attr}
instance_variable_get(:@#{attr})
end
def #{attr}=(value)
instance_variable_set(:@#{attr}, value)
notify_observers(:#{attr}, value) if respond_to?(:notify_observers)
end
def #{attr}_changed?
instance_variable_defined?(:@#{attr}_original) &&
instance_variable_get(:@#{attr}_original) != instance_variable_get(:@#{attr})
end
CODE
end
end
end
class DynamicModel
def initialize
@observers = []
end
def add_observer(observer)
@observers << observer
end
private
def notify_observers(attr, value)
@observers.each { |obs| obs.call(attr, value) }
end
end
MethodGenerator.create_accessor_methods(DynamicModel, [:name, :email, :age])
model = DynamicModel.new
model.name = "Alice"
model.email = "alice@example.com"
model.name
# => "Alice"
Common Pitfalls
Variable scope confusion represents the most frequent source of errors when working with instance_eval
. Local variables from the surrounding scope remain accessible within the evaluation block, but instance variables refer to the receiver's instance variables, not the caller's.
class Container
def initialize
@value = "container_value"
end
end
class Caller
def initialize
@value = "caller_value"
end
def demonstrate_scope_confusion
local_var = "local_value"
container = Container.new
container.instance_eval do
# This @value refers to container's @value, not caller's
puts @value
# => "container_value"
# Local variables from surrounding scope remain accessible
puts local_var
# => "local_value"
# This creates a NEW instance variable on container
@new_value = "created in eval"
end
# The caller's @value remains unchanged
puts @value
# => "caller_value"
end
end
Caller.new.demonstrate_scope_confusion
Method visibility changes when methods are defined within instance_eval
blocks. Methods defined this way become public on the receiver's singleton class, regardless of the surrounding visibility context.
class SecretKeeper
private
def add_secret_methods
instance_eval do
# This method becomes PUBLIC on the singleton class
def reveal_secret
"The secret is exposed!"
end
end
end
private
def intended_private_method
"This stays private"
end
end
keeper = SecretKeeper.new
keeper.send(:add_secret_methods)
# Method is public despite being defined in a private context
keeper.reveal_secret
# => "The secret is exposed!"
# Original private method remains private
keeper.intended_private_method
# NoMethodError: private method called
Performance degradation occurs when instance_eval
with string arguments gets called repeatedly. Ruby must parse and compile the string each time, creating significant overhead compared to block evaluation.
class PerformanceDemo
def initialize
@counter = 0
end
def slow_increment
# String parsing happens every call
instance_eval("@counter += 1")
end
def fast_increment
# Block compiled once, reused
instance_eval { @counter += 1 }
end
attr_reader :counter
end
# Benchmark shows significant difference
require 'benchmark'
demo = PerformanceDemo.new
Benchmark.bm(15) do |x|
x.report("string eval:") do
10_000.times { demo.slow_increment }
end
demo.instance_variable_set(:@counter, 0)
x.report("block eval:") do
10_000.times { demo.fast_increment }
end
end
Memory leaks can develop when instance_eval
creates closures that retain references to large objects. The block maintains access to the surrounding scope, preventing garbage collection of referenced objects.
class MemoryLeakDemo
def create_leak
large_data = Array.new(100_000) { |i| "data_#{i}" }
instance_eval do
# This closure retains reference to large_data
define_singleton_method(:process) do
# Even though large_data isn't used here,
# it remains in memory due to closure scope
"Processing complete"
end
end
# large_data cannot be garbage collected
# because the closure still references it
end
def create_no_leak
large_data = Array.new(100_000) { |i| "data_#{i}" }
# Process data immediately and don't retain reference
processed_count = large_data.size
instance_eval do
# Closure only captures processed_count, not large_data
define_singleton_method(:get_count) do
processed_count
end
end
# large_data can be garbage collected
end
end
Testing Strategies
Testing code that uses instance_eval
requires careful consideration of context boundaries and state isolation. Test frameworks themselves often use instance_eval
to provide clean test syntax, which can create nested evaluation contexts.
require 'minitest/autorun'
class InstanceEvalTest < Minitest::Test
def setup
@test_object = TestTarget.new
end
def test_instance_eval_modifies_target_state
initial_value = @test_object.instance_eval { @internal_value }
assert_nil initial_value
@test_object.instance_eval { @internal_value = "modified" }
final_value = @test_object.instance_eval { @internal_value }
assert_equal "modified", final_value
end
def test_instance_eval_creates_singleton_methods
refute @test_object.respond_to?(:dynamic_method)
@test_object.instance_eval do
def dynamic_method
"dynamically created"
end
end
assert @test_object.respond_to?(:dynamic_method)
assert_equal "dynamically created", @test_object.dynamic_method
end
def test_instance_eval_preserves_closure_variables
external_value = "from_closure"
result = @test_object.instance_eval do
@internal_value = external_value
@internal_value
end
assert_equal "from_closure", result
end
end
class TestTarget
def get_internal_value
@internal_value
end
end
Mock objects and test doubles must account for instance_eval
behavior when methods are dynamically defined. Standard mocking frameworks may not intercept singleton methods created through instance_eval
.
class MockingInstanceEval < Minitest::Test
def test_mocking_instance_eval_behavior
mock_target = Minitest::Mock.new
# Standard expectation works for normal method calls
mock_target.expect(:regular_method, "mocked_result")
# But this won't work for instance_eval created methods
real_object = RealTarget.new
# Create a wrapper that tracks instance_eval calls
wrapper = InstanceEvalWrapper.new(real_object)
wrapper.instance_eval do
def test_method
"eval_result"
end
end
assert_equal "eval_result", wrapper.test_method
assert wrapper.singleton_methods_created.include?(:test_method)
end
end
class InstanceEvalWrapper
def initialize(target)
@target = target
@singleton_methods_created = []
end
attr_reader :singleton_methods_created
def instance_eval(&block)
methods_before = singleton_methods
result = super(&block)
methods_after = singleton_methods
@singleton_methods_created.concat(methods_after - methods_before)
result
end
def method_missing(method_name, *args, &block)
@target.send(method_name, *args, &block)
end
end
class RealTarget
def regular_method
"real_result"
end
end
Integration testing with instance_eval
focuses on verifying that the dynamic behavior integrates correctly with the rest of the system. This often involves testing DSL-like interfaces and configuration systems.
class DSLIntegrationTest < Minitest::Test
def test_configuration_dsl
config = ConfigurationDSL.new
result = config.setup do
database do
host "localhost"
port 5432
pool_size 10
end
cache do
type "redis"
url "redis://localhost:6379"
ttl 3600
end
end
expected_config = {
database: { host: "localhost", port: 5432, pool_size: 10 },
cache: { type: "redis", url: "redis://localhost:6379", ttl: 3600 }
}
assert_equal expected_config, result
end
def test_error_handling_in_dsl
config = ConfigurationDSL.new
error = assert_raises(ArgumentError) do
config.setup do
database do
invalid_option "value"
end
end
end
assert_match(/Unknown configuration option/, error.message)
end
end
class ConfigurationDSL
def setup(&block)
@config = {}
instance_eval(&block)
@config
end
def database(&block)
@config[:database] = DatabaseConfigSection.new.instance_eval(&block)
end
def cache(&block)
@config[:cache] = CacheConfigSection.new.instance_eval(&block)
end
end
class DatabaseConfigSection
def initialize
@settings = {}
end
def host(value); @settings[:host] = value; @settings; end
def port(value); @settings[:port] = value; @settings; end
def pool_size(value); @settings[:pool_size] = value; @settings; end
def method_missing(method_name, *args)
raise ArgumentError, "Unknown configuration option: #{method_name}"
end
end
class CacheConfigSection
def initialize
@settings = {}
end
def type(value); @settings[:type] = value; @settings; end
def url(value); @settings[:url] = value; @settings; end
def ttl(value); @settings[:ttl] = value; @settings; end
def method_missing(method_name, *args)
raise ArgumentError, "Unknown configuration option: #{method_name}"
end
end
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#instance_eval(string, filename=nil, lineno=1) |
string (String), filename (String), lineno (Integer) |
Object |
Evaluates string within receiver's context |
#instance_eval(&block) |
block (Proc) |
Object |
Evaluates block within receiver's context |
Related Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#instance_exec(*args, &block) |
args (Array), block (Proc) |
Object |
Executes block with arguments in receiver's context |
#class_eval(string, filename=nil, lineno=1) |
string (String), filename (String), lineno (Integer) |
Object |
Evaluates string within class/module context |
#module_eval(&block) |
block (Proc) |
Object |
Alias for class_eval with block |
#define_singleton_method(symbol, &block) |
symbol (Symbol), block (Proc) |
Method |
Defines singleton method on receiver |
Context Access Patterns
Pattern | Example | Access Level |
---|---|---|
Instance Variables | obj.instance_eval { @var } |
Direct read/write |
Private Methods | obj.instance_eval { private_method } |
Full access |
Singleton Methods | obj.instance_eval { def new_method; end } |
Creates public singleton method |
Class Variables | obj.instance_eval { @@class_var } |
Access through class hierarchy |
Common Use Cases
Use Case | Implementation Pattern | Example Context |
---|---|---|
DSL Creation | config.instance_eval(&block) |
Configuration systems |
Testing Access | object.instance_eval { @private_var } |
Unit test verification |
Method Injection | target.instance_eval { def method; end } |
Monkey patching |
Proxy Delegation | @target.instance_eval(&block) |
Decorator pattern |
Template Processing | template.instance_eval(code_string) |
Code generation |
Error Types
Error | Cause | Prevention |
---|---|---|
NoMethodError |
Calling undefined method in eval context | Verify method existence with respond_to? |
NameError |
Referencing undefined variable/constant | Check variable scope and definition |
SyntaxError |
Invalid Ruby code in string eval | Validate string syntax before evaluation |
LocalJumpError |
Invalid block control flow | Ensure proper block structure |
ArgumentError |
Wrong number of arguments to eval | Match parameter expectations |
Performance Characteristics
Operation | Relative Performance | Memory Impact |
---|---|---|
Block evaluation | Baseline (fastest) | Low - compiled once |
String evaluation | 10-50x slower | Medium - parsing overhead |
Repeated string eval | 50-200x slower | High - repeated compilation |
Closure creation | 2-5x slower | High - retains scope references |
Singleton method definition | 3-8x slower | Medium - method table updates |
Scope Resolution Order
Variable Type | Resolution Priority | Access Pattern |
---|---|---|
Local variables | 1 (Highest) | From surrounding scope |
Instance variables | 2 | From receiver object |
Class variables | 3 | Through receiver's class hierarchy |
Constants | 4 | Through lexical scope and ancestors |
Global variables | 5 (Lowest) | Global namespace |