Skip to main content
⚡ Calmops

instance_eval, instance_exec, class_eval, and module_eval in Ruby

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

Comments