Overview
Ruby's eval
family of methods executes string representations of Ruby code within specified contexts. The core method Kernel#eval
takes a string containing Ruby code and evaluates it as if it were written directly in the program. Ruby provides several variants including instance_eval
, class_eval
(alias module_eval
), and eval
itself.
The evaluation occurs within a binding - the execution context that determines variable scope, method availability, and the value of self
. Ruby creates bindings automatically, but programmers can capture and manipulate them explicitly using Kernel#binding
.
code = "2 + 3"
result = eval(code)
# => 5
x = 10
eval("x * 2")
# => 20
The eval
method executes code in the current binding by default, accessing local variables and methods available at the call site. Object-specific evaluation methods like instance_eval
execute code within the context of a particular object, changing the value of self
and method availability.
class Calculator
def initialize(value)
@value = value
end
end
calc = Calculator.new(15)
calc.instance_eval("@value * 2")
# => 30
Ruby's evaluation methods form the foundation for many metaprogramming techniques including dynamic method definition, configuration DSLs, and template systems. The methods differ primarily in their execution context and how they handle the binding.
Basic Usage
The Kernel#eval
method accepts a string of Ruby code and executes it within the current or specified binding. The method returns the value of the last expression evaluated, following standard Ruby return semantics.
# Simple arithmetic evaluation
eval("5 + 3")
# => 8
# Variable access in current scope
name = "Ruby"
greeting = eval('"Hello, #{name}!"')
# => "Hello, Ruby!"
# Method calls within evaluation
def multiply(a, b)
a * b
end
eval("multiply(4, 6)")
# => 24
The instance_eval
method executes code within the context of a specific object, changing self
to that object and providing access to its instance variables and private methods.
class Person
def initialize(name)
@name = name
end
private
def secret_method
"Secret: #{@name}"
end
end
person = Person.new("Alice")
person.instance_eval do
@name.upcase
end
# => "ALICE"
person.instance_eval("secret_method")
# => "Secret: Alice"
Class and module evaluation using class_eval
(or module_eval
) executes code within the context of a class or module, allowing dynamic method definitions and constant access.
class Calculator
end
Calculator.class_eval do
def add(a, b)
a + b
end
PRECISION = 2
end
calc = Calculator.new
calc.add(2.5, 3.7)
# => 6.2
Calculator::PRECISION
# => 2
Binding objects capture execution context for later evaluation. The binding
method returns a Binding object representing the current scope, which can be passed to eval
for execution in that specific context.
def create_binding(value)
local_var = value
binding
end
stored_binding = create_binding(42)
eval("local_var * 2", stored_binding)
# => 84
# The local variable isn't accessible outside the binding
begin
local_var
rescue NameError => e
e.message
end
# => "undefined local variable or method `local_var'"
Advanced Usage
Dynamic method definition through eval
enables runtime creation of methods based on configuration, data, or patterns. The evaluation context determines where methods are defined and their accessibility.
class ApiClient
ENDPOINTS = %w[users posts comments]
ENDPOINTS.each do |endpoint|
class_eval <<-RUBY
def fetch_#{endpoint}(id = nil)
path = id ? "/#{endpoint}/\#{id}" : "/#{endpoint}"
make_request(path)
end
def create_#{endpoint}(data)
make_request("/#{endpoint}", :post, data)
end
RUBY
end
private
def make_request(path, method = :get, data = nil)
"#{method.upcase} #{path}" + (data ? " with #{data}" : "")
end
end
client = ApiClient.new
client.fetch_users(123)
# => "GET /users/123"
client.create_posts({title: "New Post"})
# => "POST /posts with {:title=>\"New Post\"}"
Template processing systems use eval
with binding objects to render dynamic content. The binding provides variable access while maintaining isolation between different evaluation contexts.
class TemplateRenderer
def initialize(template)
@template = template
end
def render(variables = {})
template_binding = create_template_binding(variables)
eval(@template, template_binding)
end
private
def create_template_binding(variables)
variables.each do |name, value|
eval("#{name} = #{value.inspect}", binding)
end
binding
end
end
template = TemplateRenderer.new('"Name: #{name}, Age: #{age}"')
result = template.render(name: "Bob", age: 30)
# => "Name: Bob, Age: 30"
Configuration DSLs leverage instance_eval
to provide clean syntax for object setup. The evaluation context allows direct method calls without explicit receiver specification.
class DatabaseConfig
attr_reader :host, :port, :credentials
def initialize
@credentials = {}
end
def configure(&block)
instance_eval(&block) if block_given?
self
end
def host(value)
@host = value
end
def port(value)
@port = value
end
def username(value)
@credentials[:username] = value
end
def password(value)
@credentials[:password] = value
end
end
config = DatabaseConfig.new.configure do
host "localhost"
port 5432
username "admin"
password "secret"
end
config.host
# => "localhost"
config.credentials
# => {:username=>"admin", :password=>"secret"}
Metaprogramming libraries often combine multiple evaluation methods to create flexible APIs. The choice of evaluation method determines the execution context and available scope.
class AttributeDefiner
def self.define_attributes(target_class, attributes)
attributes.each do |attr_name, options|
target_class.class_eval do
attr_reader attr_name
if options[:calculated]
define_method("calculated_#{attr_name}") do
instance_eval(options[:calculated])
end
end
if options[:validation]
define_method("validate_#{attr_name}") do |value|
validation_binding = binding
eval("value = #{value.inspect}; #{options[:validation]}", validation_binding)
end
end
end
end
end
end
class Product
def initialize(price, quantity)
@price = price
@quantity = quantity
end
end
AttributeDefiner.define_attributes(Product, {
total_value: {
calculated: "@price * @quantity"
},
price: {
validation: "value > 0 && value < 10000"
}
})
product = Product.new(25.99, 3)
product.calculated_total_value
# => 77.97
product.validate_price(50.0)
# => true
product.validate_price(-10)
# => false
Error Handling & Debugging
String evaluation introduces syntax errors, runtime exceptions, and security vulnerabilities that require careful handling. Ruby raises SyntaxError
when the evaluated string contains invalid Ruby syntax.
def safe_eval(code, context = binding)
eval(code, context)
rescue SyntaxError => e
{ error: "Syntax error: #{e.message}" }
rescue StandardError => e
{ error: "Runtime error: #{e.message}" }
end
safe_eval("2 + 2")
# => 4
safe_eval("2 +")
# => {:error=>"Syntax error: (eval):1: syntax error, unexpected end-of-input"}
safe_eval("undefined_method")
# => {:error=>"Runtime error: undefined local variable or method `undefined_method'"}
Input validation prevents code injection attacks when evaluating user-provided strings. Whitelist approaches restrict allowed operations while blacklist approaches block dangerous patterns.
class SafeCalculator
ALLOWED_OPERATIONS = %w[+ - * / % ** ( ) . to_i to_f abs round ceil floor]
ALLOWED_NUMBERS = /\A\d+(\.\d+)?\z/
DANGEROUS_PATTERNS = [
/system|exec|`|eval|send|method|const_get|class_eval|instance_eval/i,
/File|IO|Dir|Process|Kernel|Object/i,
/@|::/
]
def self.evaluate(expression)
return { error: "Empty expression" } if expression.strip.empty?
if dangerous_code?(expression)
return { error: "Prohibited operation detected" }
end
unless valid_tokens?(expression)
return { error: "Invalid characters or operations" }
end
begin
result = eval(expression)
{ result: result }
rescue StandardError => e
{ error: "Calculation error: #{e.message}" }
end
end
private
def self.dangerous_code?(expression)
DANGEROUS_PATTERNS.any? { |pattern| expression.match?(pattern) }
end
def self.valid_tokens?(expression)
tokens = expression.scan(/\w+|[+\-*\/%()]/)
tokens.all? do |token|
ALLOWED_OPERATIONS.include?(token) || token.match?(ALLOWED_NUMBERS)
end
end
end
SafeCalculator.evaluate("2 + 3 * 4")
# => {:result=>14}
SafeCalculator.evaluate("system('rm -rf /')")
# => {:error=>"Prohibited operation detected"}
SafeCalculator.evaluate("File.delete('important.txt')")
# => {:error=>"Prohibited operation detected"}
Binding isolation prevents evaluation from accessing or modifying unintended variables. Creating clean bindings limits the scope available to evaluated code.
class IsolatedEvaluator
def self.create_clean_binding
Module.new.module_eval { binding }
end
def self.evaluate_with_context(code, allowed_variables = {})
clean_binding = create_clean_binding
allowed_variables.each do |name, value|
clean_binding.local_variable_set(name, value)
end
begin
eval(code, clean_binding)
rescue StandardError => e
raise SecurityError, "Evaluation failed: #{e.message}"
end
end
end
# This works - only allowed variables accessible
result = IsolatedEvaluator.evaluate_with_context(
"x + y",
{ x: 10, y: 5 }
)
# => 15
# This fails - no access to external variables
secret_value = "confidential"
begin
IsolatedEvaluator.evaluate_with_context("secret_value")
rescue SecurityError => e
e.message
end
# => "Evaluation failed: undefined local variable or method `secret_value'"
Timeout mechanisms prevent infinite loops and resource exhaustion in evaluated code. Ruby's Timeout
module provides execution time limits.
require 'timeout'
class TimedEvaluator
DEFAULT_TIMEOUT = 5
def self.evaluate(code, timeout_seconds = DEFAULT_TIMEOUT)
Timeout.timeout(timeout_seconds) do
eval(code)
end
rescue Timeout::Error
{ error: "Evaluation timed out after #{timeout_seconds} seconds" }
rescue StandardError => e
{ error: "Error: #{e.message}" }
end
end
TimedEvaluator.evaluate("2 + 2")
# => 4
TimedEvaluator.evaluate("loop { sleep 1 }", 2)
# => {:error=>"Evaluation timed out after 2 seconds"}
Common Pitfalls
Variable scope confusion occurs when evaluated code expects different variable availability than the current binding provides. Local variables, instance variables, and constants follow different scoping rules during evaluation.
class ScopeDemo
def initialize
@instance_var = "instance"
end
def demonstrate_scope_issues
local_var = "local"
# This works - access to local variable
result1 = eval("local_var")
# This fails - instance_eval changes scope
begin
self.instance_eval("local_var")
rescue NameError
result2 = "local variable not accessible in instance_eval"
end
# This works - instance variable accessible in instance_eval
result3 = self.instance_eval("@instance_var")
[result1, result2, result3]
end
end
demo = ScopeDemo.new
demo.demonstrate_scope_issues
# => ["local", "local variable not accessible in instance_eval", "instance"]
String versus block evaluation produces different behavior, particularly with variable capture and performance. Blocks capture their lexical scope while strings are parsed and compiled each time.
class EvaluationComparison
def compare_approaches(value)
results = {}
# String evaluation - parsed each time
start_time = Time.now
1000.times { eval("#{value} * 2") }
results[:string_time] = Time.now - start_time
# Block evaluation - compiled once
start_time = Time.now
block = proc { value * 2 }
1000.times { block.call }
results[:block_time] = Time.now - start_time
results[:performance_ratio] = results[:string_time] / results[:block_time]
results
end
end
comparison = EvaluationComparison.new
results = comparison.compare_approaches(42)
# Block evaluation is typically 10-100x faster than string evaluation
Security vulnerabilities arise when user input reaches evaluation without proper sanitization. Code injection attacks can execute arbitrary system commands or access sensitive data.
# DANGEROUS - never do this with user input
def dangerous_calculator(expression)
eval(expression) # User could input: "system('rm -rf /')"
end
# BETTER - input validation and restricted context
def safer_calculator(expression)
# Validate input contains only safe characters
unless expression.match?(/\A[\d\s+\-*\/\(\)\.]+\z/)
raise ArgumentError, "Invalid characters in expression"
end
# Use restricted binding
restricted_binding = BasicObject.new.instance_eval { binding }
eval(expression, restricted_binding)
end
safer_calculator("2 + 3 * 4")
# => 14
begin
safer_calculator("system('echo hacked')")
rescue ArgumentError => e
e.message
end
# => "Invalid characters in expression"
Method definition context confusion occurs when eval
executes in unexpected scopes, creating methods in wrong classes or modules.
class MethodDefinitionIssues
def self.demonstrate_context_problems
# This defines method on the class
class_eval("def class_method; 'class method'; end")
# This attempts to define on instance but fails
instance = new
begin
instance.eval("def instance_method; 'instance method'; end")
rescue NoMethodError
puts "Cannot define methods with eval on instances"
end
# Correct approach for instance method definition
instance.instance_eval do
def singleton_method
"singleton method"
end
end
{
class_responds: respond_to?(:class_method),
instance_responds: instance.respond_to?(:singleton_method)
}
end
end
MethodDefinitionIssues.demonstrate_context_problems
# => {:class_responds=>true, :instance_responds=>true}
Binding lifetime issues occur when binding objects retain references to large objects or sensitive data longer than necessary, causing memory leaks or security concerns.
class BindingLifetimeDemo
def create_problematic_binding
large_data = Array.new(1000000) { rand }
sensitive_password = "secret123"
# Binding captures all local variables
binding
end
def create_clean_binding(needed_value)
# Only capture what's needed
value = needed_value
# Clear other variables before creating binding
binding
end
def demonstrate_memory_impact
# Problematic - binding holds references to large objects
binding1 = create_problematic_binding
# Better - binding only holds necessary data
binding2 = create_clean_binding(42)
# Bindings retain their variable references indefinitely
eval("large_data.size rescue 'not accessible'", binding1)
# => 1000000
eval("large_data.size rescue 'not accessible'", binding2)
# => "not accessible"
end
end
Reference
Core Evaluation Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Kernel#eval(string, binding=nil, filename="(eval)", lineno=1) |
string (String), binding (Binding), filename (String), lineno (Integer) |
Object | Evaluates Ruby code string in specified or current binding |
Object#instance_eval(string, filename="(eval)", lineno=1) |
string (String), filename (String), lineno (Integer) |
Object | Evaluates string in context of receiver object |
Object#instance_eval { block } |
block (Proc) |
Object | Evaluates block in context of receiver object |
Module#class_eval(string, filename="(eval)", lineno=1) |
string (String), filename (String), lineno (Integer) |
Object | Evaluates string in context of class/module |
Module#class_eval { block } |
block (Proc) |
Object | Evaluates block in context of class/module |
Module#module_eval |
Same as class_eval |
Object | Alias for class_eval |
Binding Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Kernel#binding |
None | Binding | Returns binding object for current execution context |
Binding#eval(string, filename="(eval)", lineno=1) |
string (String), filename (String), lineno (Integer) |
Object | Evaluates string within this binding |
Binding#local_variables |
None | Array | Returns array of local variable names in binding |
Binding#local_variable_get(symbol) |
symbol (Symbol) |
Object | Gets value of local variable from binding |
Binding#local_variable_set(symbol, obj) |
symbol (Symbol), obj (Object) |
Object | Sets local variable in binding |
Binding#local_variable_defined?(symbol) |
symbol (Symbol) |
Boolean | Checks if local variable exists in binding |
Binding#receiver |
None | Object | Returns the receiver object (value of self ) |
Exception Classes
Exception | Inheritance | Common Causes |
---|---|---|
SyntaxError |
StandardError | Invalid Ruby syntax in evaluated string |
NameError |
StandardError | Reference to undefined variable or method |
NoMethodError |
NameError | Method call on object that doesn't respond |
ArgumentError |
StandardError | Wrong number or type of arguments |
SecurityError |
StandardError | Security policy violations during evaluation |
Security Considerations
Risk | Mitigation Strategy | Implementation |
---|---|---|
Code Injection | Input validation | Whitelist allowed characters/operations |
System Access | Restricted binding | Use clean binding without system access |
Resource Exhaustion | Timeout limits | Wrap evaluation in Timeout block |
Memory Leaks | Binding cleanup | Clear unnecessary variables before creating binding |
Data Exposure | Variable isolation | Use module-based clean bindings |
Performance Characteristics
Operation | Relative Cost | Optimization Strategy |
---|---|---|
String eval | High (parsing + compilation) | Cache compiled code when possible |
Block eval | Low (pre-compiled) | Prefer blocks over strings |
Binding creation | Medium | Reuse bindings when safe |
Variable access | Low | Direct access faster than eval |
Method definition | High (class modification) | Batch multiple definitions |
Evaluation Context Matrix
Method | self Value |
Local Variables | Instance Variables | Constants | Method Definition Target |
---|---|---|---|---|---|
eval |
Current self | Current scope | Current object | Current scope | Current class/module |
instance_eval |
Receiver object | Not accessible | Receiver's vars | Current scope | Receiver's singleton class |
class_eval |
Class/module | Not accessible | Not accessible | Class/module scope | Class/module |
eval(string, binding) |
Binding's self | Binding's scope | Binding's object | Binding's scope | Binding's context |