Overview
Ruby's eval
method executes a string containing Ruby code within a specified context, while the Binding
class captures the execution context including local variables, instance variables, and method definitions. Together, these features enable dynamic code execution and metaprogramming patterns.
The eval
method accepts a string of Ruby code and optionally a Binding
object that defines the execution context. When no binding is provided, eval
executes code in the current context. The Binding
class encapsulates the execution environment at a specific point in the program, preserving variable scope and method accessibility.
code = "x + y"
x = 10
y = 20
result = eval(code)
# => 30
Binding
objects are created using the binding
method, which captures the current execution context:
def create_context
local_var = "captured"
binding
end
context = create_context
eval("local_var", context)
# => "captured"
Ruby provides several methods to obtain bindings: binding
returns the current context, Proc#binding
returns the context where a proc was defined, and Method#binding
returns the context of method definitions. Each binding maintains its own variable scope and method visibility.
Basic Usage
The eval
method transforms strings into executable Ruby code. Without additional arguments, eval
executes code in the current context, accessing local variables and methods:
def greet(name)
template = "Hello, #{name}!"
eval("template.upcase")
end
greet("Ruby")
# => "HELLO, RUBY!"
Binding
objects capture the complete execution state. The binding
method creates a binding for the current context:
def calculator(a, b)
operation = "+"
current_binding = binding
code = "a #{operation} b"
eval(code, current_binding)
end
calculator(15, 25)
# => 40
Bindings preserve local variable values at the time of creation. Variables assigned after binding creation remain inaccessible unless explicitly updated:
x = 100
saved_context = binding
x = 200
eval("x", saved_context)
# => 100
eval("x = 300", saved_context)
eval("x", saved_context)
# => 300
Class and module contexts create bindings with different scoping rules. Instance variables and class variables become accessible within their respective binding contexts:
class Calculator
def initialize
@base = 10
end
def compute(formula)
eval(formula, binding)
end
end
calc = Calculator.new
calc.compute("@base * 5")
# => 50
Advanced Usage
Complex metaprogramming scenarios require careful binding manipulation. Closures maintain access to their definition context through their associated bindings:
def create_evaluator(context_vars)
context_vars.each { |k, v| eval("#{k} = #{v.inspect}", binding) }
lambda do |expression|
eval(expression, binding)
end
end
evaluator = create_evaluator(x: 10, y: 20, z: 30)
evaluator.call("x * y + z")
# => 230
Method definitions within eval create methods in the appropriate scope. The binding context determines where methods are defined:
class DynamicMethods
def self.add_method(name, body)
class_eval <<-RUBY
def #{name}
#{body}
end
RUBY
end
end
DynamicMethods.add_method("triple", "value * 3")
obj = DynamicMethods.new
obj.define_singleton_method(:value) { 7 }
obj.triple
# => 21
Nested binding contexts create layered scoping environments. Inner contexts inherit outer variables while maintaining isolation:
def nested_evaluation
outer = "outer_scope"
inner_proc = proc do
inner = "inner_scope"
eval("outer + ' -> ' + inner", binding)
end
inner_proc.call
end
nested_evaluation
# => "outer_scope -> inner_scope"
Template systems use binding contexts to provide variable access within templates. The binding captures the template environment:
class TemplateEngine
def initialize(context_object)
@context = context_object
end
def render(template)
eval(template, @context.instance_eval { binding })
end
end
class User
def initialize(name, age)
@name, @age = name, age
end
private
def greeting
"Hello, I'm #{@name}"
end
end
user = User.new("Alice", 30)
engine = TemplateEngine.new(user)
engine.render("greeting + ' and I am ' + @age.to_s + ' years old'")
# => "Hello, I'm Alice and I am 30 years old"
Error Handling & Debugging
SyntaxError
occurs when eval receives malformed Ruby code. The error includes the problematic code and line information:
begin
eval("invalid syntax here @@")
rescue SyntaxError => e
puts e.message
# => (eval):1: syntax error, unexpected end-of-input
end
NameError
arises when eval references undefined variables or methods within the binding context:
binding_context = binding
begin
eval("undefined_variable", binding_context)
rescue NameError => e
puts e.message
# => undefined local variable or method `undefined_variable'
end
Debugging dynamically executed code requires careful error context preservation. Stack traces from eval show the evaluation point rather than the original code location:
def debug_eval(code, context = binding)
eval(code, context)
rescue StandardError => e
puts "Error in: #{code}"
puts "Context: #{context.eval('local_variables')}"
puts "Error: #{e.class} - #{e.message}"
raise
end
def test_method
local_var = "test"
debug_eval("raise 'Custom error in eval'")
end
begin
test_method
rescue RuntimeError => e
# Shows debugging information before re-raising
end
Variable scope debugging within bindings requires introspection methods. The binding provides access to local variables and their current values:
def inspect_binding_context(binding_obj)
{
local_variables: binding_obj.local_variables,
local_values: binding_obj.local_variables.map { |var|
[var, binding_obj.local_variable_get(var)]
}.to_h
}
end
def sample_method
x = 10
y = "hello"
context = binding
inspect_binding_context(context)
end
sample_method
# => {
# local_variables: [:x, :y, :context],
# local_values: {:x=>10, :y=>"hello", :context=>#<Binding:...>}
# }
Common Pitfalls
Security vulnerabilities represent the most critical pitfall with eval. User-provided input executed through eval enables code injection attacks:
# DANGEROUS - Never do this
def unsafe_calculator(expression)
eval(expression) # Allows arbitrary code execution
end
# User input: "system('rm -rf /')" would be devastating
Safe evaluation requires input sanitization and restricted contexts. Create isolated binding contexts without dangerous methods:
class SafeEvaluator
ALLOWED_OPERATIONS = %w[+ - * / % ** < > <= >= == !=].freeze
def self.safe_eval(expression, variables = {})
# Validate expression contains only safe operations
unless expression.match?(/\A[\w\s#{Regexp.escape(ALLOWED_OPERATIONS.join)}()]+\z/)
raise ArgumentError, "Unsafe expression"
end
# Create restricted context
context = Object.new.instance_eval do
variables.each { |k, v| define_singleton_method(k) { v } }
binding
end
eval(expression, context)
end
end
Variable scope confusion occurs when binding contexts don't contain expected variables. Bindings capture variable state at creation time, not current state:
# Confusing behavior
results = []
(1..3).each do |i|
# All bindings capture the same 'i' reference
results << binding
end
# Later evaluation shows unexpected results
results.map { |b| eval("i", b) }
# => [3, 3, 3] - all show final value of i
Performance degradation affects applications using eval extensively. String evaluation overhead and lack of optimization create bottlenecks:
# Inefficient repeated evaluation
def slow_template(data)
template = "#{data[:name]} is #{data[:age]} years old"
1000.times { eval("\"#{template}\"") }
end
# Better approach using direct interpolation
def fast_template(data)
1000.times { "#{data[:name]} is #{data[:age]} years old" }
end
Context leakage occurs when bindings retain references to large objects or sensitive data. Bindings prevent garbage collection of referenced objects:
def create_leaky_binding
large_data = Array.new(1_000_000) { rand }
sensitive_password = "secret123"
# Binding retains references to large_data and sensitive_password
binding
end
# These objects remain in memory through the binding
leaked_context = create_leaky_binding
# large_data and sensitive_password cannot be garbage collected
Production Patterns
Domain Specific Languages (DSLs) use eval and binding for configuration and rule definition. The binding provides variable context for DSL execution:
class ConfigurationDSL
def initialize(&block)
@config = {}
instance_eval(&block) if block_given?
end
def set(key, value)
@config[key] = value
end
def get(key)
@config[key]
end
def conditional(condition, &block)
instance_eval(&block) if eval(condition, binding)
end
end
config = ConfigurationDSL.new do
set :database_url, "postgresql://localhost/app"
set :debug_mode, true
conditional "get(:debug_mode)" do
set :log_level, "DEBUG"
end
end
Template systems in web frameworks use binding contexts to provide view variables. The binding contains instance variables and helper methods:
class ViewRenderer
def initialize(view_context)
@context = view_context
end
def render_template(template_string)
binding_context = @context.instance_eval { binding }
eval(template_string, binding_context)
end
end
class ViewContext
def initialize(user:, posts:)
@user = user
@posts = posts
end
private
def format_date(date)
date.strftime("%B %d, %Y")
end
def pluralize(count, word)
count == 1 ? "#{count} #{word}" : "#{count} #{word}s"
end
end
# Usage in template
template = '"Welcome #{@user.name}! You have #{pluralize(@posts.count, \"post\")}."'
context = ViewContext.new(user: OpenStruct.new(name: "Alice"), posts: [1, 2, 3])
renderer = ViewRenderer.new(context)
renderer.render_template(template)
# => "Welcome Alice! You have 3 posts."
Dynamic method definition in frameworks uses class_eval with binding contexts. This pattern enables plugin architectures and runtime configuration:
module DynamicAttributes
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def attr_calculated(name, formula)
define_method(name) do
instance_eval(formula)
end
end
def attr_validated(name, validation_rule)
attr_accessor name
define_method("#{name}=") do |value|
unless instance_exec(value) { |v| eval(validation_rule) }
raise ArgumentError, "Invalid #{name}: #{value}"
end
instance_variable_set("@#{name}", value)
end
end
end
end
class Product
include DynamicAttributes
attr_accessor :price, :tax_rate
attr_calculated :total_price, "@price * (1 + @tax_rate)"
attr_validated :price, "v > 0 && v < 10000"
end
product = Product.new
product.tax_rate = 0.1
product.price = 100
product.total_price
# => 110.0
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
eval(string, binding=nil, filename="(eval)", lineno=1) |
string (String), binding (Binding), filename (String), lineno (Integer) |
Object |
Evaluates Ruby code string in specified binding context |
binding |
None | Binding |
Returns current execution context as Binding object |
Binding#eval(string, filename="(eval)", lineno=1) |
string (String), filename (String), lineno (Integer) |
Object |
Evaluates string within the binding's context |
Binding#local_variables |
None | Array<Symbol> |
Returns array of local variable names in binding |
Binding#local_variable_get(symbol) |
symbol (Symbol) |
Object |
Gets value of local variable by name |
Binding#local_variable_set(symbol, obj) |
symbol (Symbol), obj (Object) |
Object |
Sets local variable value, returns the value |
Binding#local_variable_defined?(symbol) |
symbol (Symbol) |
Boolean |
Checks if local variable is defined in binding |
Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Object#instance_eval(string, filename="(eval)", lineno=1) |
string (String), filename (String), lineno (Integer) |
Object |
Evaluates string in context of receiver object |
Object#instance_exec(*args, &block) |
*args (Object), &block (Proc) |
Object |
Executes block in context of receiver with arguments |
Module#class_eval(string, filename="(eval)", lineno=1) |
string (String), filename (String), lineno (Integer) |
Object |
Evaluates string in context of class/module |
Module#module_eval(string, filename="(eval)", lineno=1) |
string (String), filename (String), lineno (Integer) |
Object |
Alias for class_eval |
Proc#binding |
None | Binding |
Returns binding context where proc was defined |
Common Exceptions
Exception | Trigger Condition | Description |
---|---|---|
SyntaxError |
Malformed Ruby syntax in eval string | Raised when string contains invalid Ruby code |
NameError |
Reference to undefined variable/method | Occurs when eval accesses non-existent identifiers |
NoMethodError |
Method call on inappropriate object | Subclass of NameError for method-specific errors |
ArgumentError |
Invalid argument to binding methods | Raised by local_variable_get with invalid symbol |
SecurityError |
Tainted string evaluation (safe level) | Historical security restriction in older Ruby versions |
Binding Context Access
Context Element | Access Method | Returns | Description |
---|---|---|---|
Local Variables | local_variables |
Array<Symbol> |
Names of local variables in scope |
Variable Value | local_variable_get(name) |
Object |
Value of specific local variable |
Variable Check | local_variable_defined?(name) |
Boolean |
Whether local variable exists |
Self Reference | eval("self") |
Object |
The receiver object in binding context |
Instance Variables | eval("instance_variables") |
Array<Symbol> |
Instance variables accessible in binding |
Class Variables | eval("self.class.class_variables") |
Array<Symbol> |
Class variables visible in context |