CrackedRuby logo

CrackedRuby

class_eval and module_eval

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