Overview
Ruby provides class_eval
and module_eval
methods for executing code within the context of a class or module. These methods evaluate strings or blocks in the scope of the receiver, allowing dynamic modification of class and module definitions at runtime.
Both methods operate identically - class_eval
works on classes while module_eval
works on modules. Ruby also provides instance_eval
as a companion method that evaluates code in the context of a specific object instance rather than a class or module.
The methods accept either a string containing Ruby code or a block. When passed a string, Ruby parses and executes the code as if it were written inside the class or module definition. When passed a block, the block executes with the class or module as self
.
class User
end
User.class_eval do
def name
@name
end
def name=(value)
@name = value
end
end
user = User.new
user.name = "Alice"
puts user.name
# => Alice
String evaluation supports filename and line number parameters for error reporting:
User.class_eval("def age; @age; end", "user_extensions.rb", 10)
Both methods provide access to class variables, instance variables, constants, and methods defined within the target scope. The evaluation occurs at the class level, making newly defined methods available as instance methods.
Basic Usage
The primary use case involves adding methods to existing classes or modules. This pattern appears frequently in libraries that extend core Ruby classes or provide domain-specific languages.
class Calculator
end
Calculator.class_eval do
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
end
calc = Calculator.new
calc.add(5, 3)
# => 8
String-based evaluation supports dynamic method generation based on external data:
methods_to_create = ['first_name', 'last_name', 'email']
class Person
end
methods_to_create.each do |method_name|
Person.class_eval <<-RUBY
def #{method_name}
@#{method_name}
end
def #{method_name}=(value)
@#{method_name} = value
end
RUBY
end
person = Person.new
person.first_name = "John"
person.email = "john@example.com"
Module extension follows the same pattern:
module Enumerable
end
Enumerable.module_eval do
def average
sum.to_f / count
end
end
[1, 2, 3, 4, 5].average
# => 3.0
Class-level method definition requires explicit self
prefix:
String.class_eval do
def self.random(length)
chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
Array.new(length) { chars.sample }.join
end
end
String.random(8)
# => "aB3xK9mP"
Both methods handle constants and nested class definitions:
User.class_eval do
VALID_STATUSES = %w[active inactive pending].freeze
class Profile
def initialize(user)
@user = user
end
end
def profile
@profile ||= Profile.new(self)
end
end
Advanced Usage
Complex metaprogramming scenarios combine class_eval
with other Ruby features like method_missing
, const_missing
, and introspection methods. These patterns enable sophisticated domain-specific languages and framework capabilities.
Dynamic class generation with conditional method creation:
class ModelBuilder
def self.create_model(name, attributes, options = {})
klass = Class.new
klass.class_eval do
attributes.each do |attr_name, attr_type|
define_method attr_name do
instance_variable_get("@#{attr_name}")
end
define_method "#{attr_name}=" do |value|
converted_value = case attr_type
when :integer then value.to_i
when :float then value.to_f
when :string then value.to_s
else value
end
instance_variable_set("@#{attr_name}", converted_value)
end
end
if options[:validations]
define_method :valid? do
options[:validations].all? do |attr, validation|
value = instance_variable_get("@#{attr}")
case validation
when :required then !value.nil?
when Proc then validation.call(value)
else true
end
end
end
end
if options[:callbacks]
options[:callbacks].each do |event, callback|
alias_method "#{event}_without_callback", event if method_defined?(event)
define_method event do |*args|
result = send("#{event}_without_callback", *args) if respond_to?("#{event}_without_callback")
instance_exec(result, &callback)
result
end
end
end
end
Object.const_set(name, klass)
end
end
ModelBuilder.create_model(
'Product',
{ name: :string, price: :float, quantity: :integer },
validations: { name: :required, price: proc { |p| p > 0 } },
callbacks: {
price: proc { |new_price| puts "Price updated to #{new_price}" }
}
)
product = Product.new
product.name = "Widget"
product.price = 29.99
# => Price updated to 29.99
product.valid?
# => true
Runtime mixin application with conditional behavior:
module Auditable
def self.included(base)
base.class_eval do
class_variable_set(:@@audit_fields, [])
def self.audit_field(field_name, options = {})
class_variable_get(:@@audit_fields) << {
name: field_name,
options: options
}
alias_method "#{field_name}_without_audit=", "#{field_name}="
define_method "#{field_name}=" do |value|
old_value = send(field_name)
result = send("#{field_name}_without_audit=", value)
if options[:track_changes] && old_value != value
audit_log = instance_variable_get(:@audit_log) || []
audit_log << {
field: field_name,
old_value: old_value,
new_value: value,
timestamp: Time.now
}
instance_variable_set(:@audit_log, audit_log)
end
result
end
end
def audit_trail
instance_variable_get(:@audit_log) || []
end
end
end
end
class Account
include Auditable
attr_accessor :balance, :status
audit_field :balance, track_changes: true
audit_field :status, track_changes: true
end
account = Account.new
account.balance = 1000
account.balance = 1500
account.status = "active"
account.audit_trail
# => [{field: :balance, old_value: nil, new_value: 1000, timestamp: ...},
# {field: :balance, old_value: 1000, new_value: 1500, timestamp: ...},
# {field: :status, old_value: nil, new_value: "active", timestamp: ...}]
Proxy pattern implementation with method forwarding:
class DatabaseProxy
def initialize(connection_params)
@connection_params = connection_params
@connection = nil
end
def method_missing(method_name, *args, **kwargs, &block)
establish_connection unless connected?
if @connection.respond_to?(method_name)
self.class.class_eval do
define_method method_name do |*method_args, **method_kwargs, &method_block|
establish_connection unless connected?
@connection.send(method_name, *method_args, **method_kwargs, &method_block)
end
end
send(method_name, *args, **kwargs, &block)
else
super
end
end
private
def establish_connection
@connection = DatabaseConnection.new(@connection_params)
end
def connected?
@connection && @connection.active?
end
end
Common Pitfalls
Several subtle behaviors in class_eval
and module_eval
create common debugging challenges. Understanding these pitfalls prevents runtime errors and unexpected behavior.
Variable scope confusion represents the most frequent issue. Local variables from the calling context remain inaccessible within string-based evaluation:
class Container
end
prefix = "user_"
# This fails - local variables not accessible in string eval
Container.class_eval "def #{prefix}name; @name; end"
# => NameError: undefined local variable or method `prefix'
# Block form preserves local variable access
Container.class_eval do
define_method "#{prefix}name" do
@name
end
end
# String interpolation before evaluation works
Container.class_eval "def #{prefix}name; @name; end"
# => Works because interpolation happens before eval
Constant resolution behaves differently between string and block evaluation:
module Outer
CONSTANT = "outer_value"
class Inner
CONSTANT = "inner_value"
# Block form looks up constants in lexical scope
class_eval do
def block_constant
CONSTANT # => "inner_value"
end
end
# String form looks up constants in evaluation context
class_eval "def string_constant; CONSTANT; end"
# Method returns "inner_value" but resolution path differs
end
end
Method visibility modifiers apply to subsequently defined methods, creating confusion:
class Example
private
# This method becomes private due to prior modifier
class_eval do
def public_method
"should be public"
end
end
# Explicit visibility restoration required
class_eval do
def another_method
"also private"
end
public :another_method
end
end
Example.new.public_method
# => NoMethodError: private method `public_method'
Return value handling differs between evaluation methods:
class ReturnTest
end
# Block form returns block's last expression
result1 = ReturnTest.class_eval do
def method1; end
"block result"
end
# => "block result"
# String form returns evaluation result
result2 = ReturnTest.class_eval("def method2; end; 'string result'")
# => "string result"
# Empty evaluation returns nil
result3 = ReturnTest.class_eval("")
# => nil
Binding captures create memory retention issues:
class MemoryLeakExample
def self.create_methods(large_data)
# Block captures large_data reference
class_eval do
large_data.each_with_index do |item, index|
define_method "item_#{index}" do
# large_data remains referenced through closure
item
end
end
end
end
end
# String evaluation avoids closure retention
class MemoryEfficientExample
def self.create_methods(large_data)
large_data.each_with_index do |item, index|
# Only item value captured, not entire array
class_eval "def item_#{index}; #{item.inspect}; end"
end
end
end
Exception location reporting causes debugging confusion:
class ErrorExample
end
begin
ErrorExample.class_eval do
def failing_method
raise "Block error"
end
end
rescue => e
puts e.backtrace.first
# => Shows location in class_eval block
end
begin
ErrorExample.class_eval("def another_failing_method; raise 'String error'; end")
rescue => e
puts e.backtrace.first
# => Shows eval location, not original source
end
# Provide filename and line for better debugging
ErrorExample.class_eval("def traced_method; raise 'Traced error'; end",
"error_example.rb", 42)
# => Backtrace shows error_example.rb:42
Class variable sharing creates unexpected state mutations:
class Parent
@@shared = []
def self.add_item(item)
@@shared << item
end
def self.items
@@shared
end
end
class Child < Parent
end
# Both classes share the same class variable
Parent.add_item("parent_item")
Child.add_item("child_item")
Parent.items
# => ["parent_item", "child_item"]
Child.class_eval do
def self.clear_items
@@shared.clear # Affects parent class too
end
end
Child.clear_items
Parent.items
# => []
Reference
Method Signatures
Method | Parameters | Returns | Description |
---|---|---|---|
class_eval(string, filename=nil, lineno=nil) |
string (String), filename (String), lineno (Integer) |
Object |
Evaluates string in class context |
class_eval(&block) |
block (Proc) |
Object |
Evaluates block in class context |
module_eval(string, filename=nil, lineno=nil) |
string (String), filename (String), lineno (Integer) |
Object |
Evaluates string in module context |
module_eval(&block) |
block (Proc) |
Object |
Evaluates block in module context |
Aliases
Method | Alias | Context |
---|---|---|
class_eval |
class_exec |
When passing arguments to block |
module_eval |
module_exec |
When passing arguments to block |
Evaluation Context
Aspect | Block Form | String Form |
---|---|---|
self |
Class/Module | Class/Module |
Local variables | Accessible from outer scope | Not accessible |
Constants | Lexical scope lookup | Runtime scope lookup |
Method definition | Creates instance methods | Creates instance methods |
Return value | Block's last expression | Evaluation result |
Common Patterns
Pattern | Implementation | Use Case |
---|---|---|
Attribute accessor | attr_reader , attr_writer , attr_accessor |
Simple property access |
Dynamic method creation | define_method with closure |
Runtime method generation |
Conditional method definition | if conditions in eval context |
Feature toggling |
Method aliasing | alias_method before redefinition |
Method decoration |
Mixin integration | include /extend with callback hooks |
Framework extension |
Error Types
Error Class | Cause | Resolution |
---|---|---|
NameError |
Undefined variable/constant in string eval | Use block form or define variables |
ArgumentError |
Wrong number of arguments | Check method signature |
SyntaxError |
Invalid Ruby syntax in string | Validate syntax before evaluation |
NoMethodError |
Method called on wrong object | Verify evaluation context |
TypeError |
Type mismatch in dynamic operations | Add type checking |
Performance Considerations
Scenario | Performance | Memory | Recommendation |
---|---|---|---|
String evaluation | Slower (parsing overhead) | Lower | Use for simple, one-time operations |
Block evaluation | Faster (pre-parsed) | Higher (closure retention) | Use for complex logic |
Repeated evaluation | Cache parsed code | Varies | Consider define_method alternatives |
Large closures | N/A | High retention | Use string form or limit scope |
Debugging Techniques
Issue | Diagnostic Approach | Tools |
---|---|---|
Scope problems | Print self and binding |
puts self.class , caller |
Constant resolution | Trace constant lookup | const_defined? , constants |
Method visibility | Check method lists | private_methods , public_methods |
Variable access | Inspect binding | local_variables , instance_variables |
Performance issues | Profile evaluation overhead | Benchmark , profiling tools |
Security Considerations
Risk | Mitigation | Example |
---|---|---|
Code injection | Validate input strings | Whitelist allowed methods |
Arbitrary code execution | Restrict string sources | Use block form when possible |
Constant pollution | Namespace isolation | Module encapsulation |
Method redefinition | Check existing methods | method_defined? before definition |