CrackedRuby logo

CrackedRuby

instance_eval

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