Ruby Meta-programming: Understanding class_eval, instance_eval, eval, define_method, and method_missing

🔍 Introduction

Ruby’s meta-programming capabilities let you write code that writes code, opening doors to DSLs (Check my post on DSL), dynamic behaviours, and DRY abstractions. In this two-part series, we’ll explore:

  • Part 1: class_eval, instance_eval, eval, define_method, and method_missing
  • Part 2: Additional metaprogramming methods like define_singleton_method, module_eval, send, and more

🏷️ Part 1: Core Metaprogramming Methods

🔨 1. What is class_eval?

class_eval is a method that allows you to evaluate a block of code or a string within the context of a class. It’s part of Ruby’s metaprogramming toolkit and is inherited from the Module class.

Basic Syntax

class MyClass
  # class definition
end

MyClass.class_eval do
  # code executed in the context of MyClass
  def new_method
    puts "Hello from class_eval!"
  end
end

Key Features

1. Context Execution

The code inside class_eval is executed as if it were written directly inside the class definition:

class Example
  def existing_method
    puts "I exist"
  end
end

Example.class_eval do
  def dynamic_method
    puts "I was added dynamically!"
  end
end

# Now you can use the new method
obj = Example.new
obj.dynamic_method  # => "I was added dynamically!"

2. String Evaluation

You can also pass a string instead of a block:

class MyClass
end

MyClass.class_eval("def hello; puts 'Hello!'; end")

3. Access to Class Variables and Constants

The evaluated code has access to the class’s variables and constants:

class Calculator
  OPERATIONS = [:add, :subtract, :multiply, :divide]
end

Calculator.class_eval do
  OPERATIONS.each do |op|
    define_method(op) do |a, b|
      case op
      when :add then a + b
      when :subtract then a - b
      when :multiply then a * b
      when :divide then a / b
      end
    end
  end
end

Common Use Cases

1. Dynamic Method Creation

class User
  attr_accessor :name, :email
end

# Dynamically add validation methods
User.class_eval do
  [:name, :email].each do |field|
    define_method("validate_#{field}") do
      !send(field).nil? && !send(field).empty?
    end
  end
end

2. Plugin Systems

class Plugin
  def self.register(plugin_name, &block)
    class_eval(&block)
  end
end

Plugin.register(:logger) do
  def log(message)
    puts "[LOG] #{message}"
  end
end

3. Configuration DSLs

class Config
  def self.configure(&block)
    class_eval(&block)
  end
end

Config.configure do
  def database_url
    "postgresql://localhost/myapp"
  end

  def api_key
    ENV['API_KEY']
  end
end

Differences from instance_eval

  • class_eval: Executes code in the context of the class (like being inside the class definition)
  • instance_eval: Executes code in the context of an instance of the class
class Example
  def self.class_method
    puts "I'm a class method"
  end

  def instance_method
    puts "I'm an instance method"
  end
end

# class_eval - can define class methods
Example.class_eval do
  def self.new_class_method
    puts "New class method"
  end
end

# instance_eval - can define instance methods
Example.instance_eval do
  def new_instance_method
    puts "New instance method"
  end
end

Security Considerations

⚠️ Warning: Using class_eval with user input can be dangerous:

# DANGEROUS - don't do this with user input
user_code = gets.chomp
MyClass.class_eval(user_code)  # Could execute malicious code

Performance Notes

  • class_eval with blocks is generally faster than with strings
  • The block version is also safer and more readable
  • Use string evaluation only when you need to evaluate dynamic code

Real-World Example

Here’s how you might use class_eval in a practical scenario:

class ActiveRecord
  def self.has_many(association_name)
    class_eval do
      define_method(association_name) do
        # Implementation for has_many association
        puts "Fetching #{association_name} for #{self.class}"
      end
    end
  end
end

class User < ActiveRecord
  has_many :posts
end

user = User.new
user.posts  # => "Fetching posts for User"

class_eval is a powerful tool for meta-programming in Ruby, allowing you to dynamically modify classes at runtime. It’s commonly used in frameworks like Rails for creating DSLs and dynamic method generation.


🛠️ 2. What is instance_eval?

instance_eval is a method that allows you to evaluate a block of code or a string within the context of a specific object instance. It’s inherited from the BasicObject class and is available on all Ruby objects.

Basic Syntax

class MyClass
  def initialize(name)
    @name = name
  end
end

obj = MyClass.new("Alice")

obj.instance_eval do
  # code executed in the context of this specific object
  puts @name  # Can access instance variables
end

Key Features

1. Instance Context Execution

The code inside instance_eval is executed as if it were an instance method of the object:

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    puts "Hi, I'm #{@name}"
  end
end

person = Person.new("Bob", 30)

person.instance_eval do
  puts "Name: #{@name}"      # => "Name: Bob"
  puts "Age: #{@age}"        # => "Age: 30"
  introduce                  # => "Hi, I'm Bob"
end

2. Access to Instance Variables

You can read and modify instance variables:

class BankAccount
  def initialize(balance)
    @balance = balance
  end
end

account = BankAccount.new(1000)

account.instance_eval do
  puts "Current balance: #{@balance}"  # => "Current balance: 1000"
  @balance += 500                      # Modify instance variable
  puts "New balance: #{@balance}"      # => "New balance: 1500"
end

3. String Evaluation

You can also pass a string instead of a block:

class Example
  def initialize(value)
    @value = value
  end
end

obj = Example.new("test")
obj.instance_eval("puts @value")  # => "test"

Common Use Cases

1. Testing Private Methods

class Calculator
  private

  def secret_calculation(x, y)
    x * y + 42
  end
end

calc = Calculator.new

# Access private method in tests
result = calc.instance_eval { secret_calculation(5, 3) }
puts result  # => 57

2. Dynamic Property Access

class Config
  def initialize
    @settings = {}
  end

  def set(key, value)
    @settings[key] = value
  end
end

config = Config.new
config.set(:api_url, "https://api.example.com")
config.set(:timeout, 30)

# Access settings dynamically
config.instance_eval do
  puts @settings[:api_url]   # => "https://api.example.com"
  puts @settings[:timeout]   # => 30
end

3. Object Inspection and Debugging

class ComplexObject
  def initialize
    @data = { a: 1, b: 2, c: 3 }
    @metadata = { created: Time.now }
  end
end

obj = ComplexObject.new

obj.instance_eval do
  puts "All instance variables:"
  instance_variables.each do |var|
    puts "#{var}: #{instance_variable_get(var)}"
  end
end

4. DSL (Domain Specific Language) Implementation

class Builder
  def initialize
    @result = []
  end

  def build(&block)
    instance_eval(&block)
    @result
  end
end

builder = Builder.new
result = builder.build do
  @result << "item1"
  @result << "item2"
  @result << "item3"
end

puts result  # => ["item1", "item2", "item3"]

Differences from class_eval

Featureinstance_evalclass_eval
ContextObject instanceClass
SelfPoints to the objectPoints to the class
Method DefinitionDefines instance methodsDefines instance methods
AccessInstance variables, private methodsClass methods, constants
class Example
  def initialize
    @value = "instance value"
  end

  @@class_var = "class variable"
end

obj = Example.new

# instance_eval - context is the object
obj.instance_eval do
  puts @value        # => "instance value"
  puts self.class    # => Example
end

# class_eval - context is the class
Example.class_eval do
  puts @@class_var   # => "class variable"
  puts self          # => Example
end

Advanced Examples

1. Method Chaining with instance_eval

class QueryBuilder
  def initialize
    @conditions = []
  end

  def where(field, value)
    @conditions << "#{field} = '#{value}'"
    self
  end

  def build
    @conditions.join(" AND ")
  end
end

query = QueryBuilder.new.instance_eval do
  where("name", "John")
  where("age", 25)
  build
end

puts query  # => "name = 'John' AND age = '25'"

2. Configuration Objects

class AppConfig
  def initialize
    @config = {}
  end

  def configure(&block)
    instance_eval(&block)
  end

  def database(url)
    @config[:database] = url
  end

  def api_key(key)
    @config[:api_key] = key
  end

  def get_config
    @config
  end
end

config = AppConfig.new
config.configure do
  database "postgresql://localhost/myapp"
  api_key ENV['API_KEY']
end

puts config.get_config

3. Object Serialization

class Serializable
  def to_hash
    result = {}
    instance_eval do
      instance_variables.each do |var|
        key = var.to_s.delete('@').to_sym
        result[key] = instance_variable_get(var)
      end
    end
    result
  end
end

class User < Serializable
  def initialize(name, email)
    @name = name
    @email = email
  end
end

user = User.new("Alice", "alice@example.com")
puts user.to_hash  # => {:name=>"Alice", :email=>"alice@example.com"}

Security Considerations

⚠️ Warning: Like class_eval, using instance_eval with user input can be dangerous:

# DANGEROUS - don't do this with user input
user_code = gets.chomp
obj.instance_eval(user_code)  # Could execute malicious code

Performance Notes

  • instance_eval with blocks is faster than with strings
  • The block version is safer and more readable
  • Use string evaluation only when necessary for dynamic code

Real-World Usage

instance_eval is commonly used in:

  • Testing frameworks (RSpec, Minitest)
  • Configuration systems (Rails initializers)
  • Builder patterns (ActiveRecord queries)
  • DSL implementations (Rake tasks, Capistrano)
# RSpec example
describe User do
  it "has a name" do
    user = User.new("John")
    expect(user.instance_eval { @name }).to eq("John")
  end
end

instance_eval is a powerful tool for metaprogramming that allows you to execute code in the context of any object, making it invaluable for testing, debugging, and creating flexible APIs.


⚡ 3. What is eval?

Ruby’s eval method, which is the most powerful and potentially dangerous meta-programming tool in Ruby.

eval is a method that evaluates a string as Ruby code in the current context. It’s inherited from the Kernel module and is available everywhere in Ruby. Unlike class_eval and instance_eval, eval executes code in the current binding (the current execution context).

Basic Syntax

# Basic eval usage
eval("puts 'Hello from eval!'")

# With variables
x = 10
y = 20
result = eval("x + y")
puts result  # => 30

Key Features

1. Current Context Execution

eval executes code in the current binding (local variables, methods, etc.):

def calculate(a, b, operation)
  eval("#{a} #{operation} #{b}")
end

puts calculate(10, 5, "+")   # => 15
puts calculate(10, 5, "*")   # => 50
puts calculate(10, 5, "-")   # => 5

2. Access to Local Variables

name = "Alice"
age = 25
city = "New York"

eval("puts \"#{name} is #{age} years old and lives in #{city}\"")
# => "Alice is 25 years old and lives in New York"

3. Dynamic Method Calls

class Calculator
  def add(x, y)
    x + y
  end

  def multiply(x, y)
    x * y
  end
end

calc = Calculator.new
method_name = "add"
args = [5, 3]

result = eval("calc.#{method_name}(#{args.join(', ')})")
puts result  # => 8

Common Use Cases

1. Configuration Files

# config.rb
APP_NAME = "MyApp"
DEBUG_MODE = true
DATABASE_URL = "postgresql://localhost/myapp"

# Loading configuration
config_content = File.read('config.rb')
eval(config_content)

puts APP_NAME      # => "MyApp"
puts DEBUG_MODE    # => true

2. Dynamic Calculations

class MathEvaluator
  def self.evaluate(expression)
    eval(expression)
  rescue => e
    "Error: #{e.message}"
  end
end

puts MathEvaluator.evaluate("2 + 3 * 4")        # => 14
puts MathEvaluator.evaluate("Math.sqrt(16)")    # => 4.0
puts MathEvaluator.evaluate("10 / 0")           # => "Error: divided by 0"

3. Template Processing

class Template
  def initialize(template)
    @template = template
  end

  def render(binding)
    eval("\"#{@template}\"", binding)
  end
end

template = Template.new("Hello <%= name %>, you are <%= age %> years old!")
name = "Bob"
age = 30

result = template.render(binding)
puts result  # => "Hello Bob, you are 30 years old!"

4. Code Generation

class CodeGenerator
  def self.generate_method(method_name, body)
    eval("
      def #{method_name}
        #{body}
      end
    ")
  end
end

class MyClass
  CodeGenerator.generate_method(:greet, "puts 'Hello, World!'")
  CodeGenerator.generate_method(:calculate, "2 + 2")
end

obj = MyClass.new
obj.greet        # => "Hello, World!"
puts obj.calculate  # => 4

Advanced Examples

1. Dynamic Class Creation

class DynamicClassCreator
  def self.create_class(class_name, methods = {})
    eval("
      class #{class_name}
        #{methods.map { |name, body| "def #{name}; #{body}; end" }.join("\n")}
      end
    ")
  end
end

DynamicClassCreator.create_class("Person", {
  greet: "puts 'Hello!'",
  age: "25"
})

person = Person.new
person.greet  # => "Hello!"
puts person.age  # => 25

2. Expression Parser

class ExpressionParser
  def self.parse(expression, variables = {})
    # Create a safe binding with variables
    binding_obj = binding
    variables.each do |key, value|
      binding_obj.local_variable_set(key, value)
    end

    eval(expression, binding_obj)
  end
end

result = ExpressionParser.parse("x * y + z", { x: 5, y: 3, z: 10 })
puts result  # => 25

3. Configuration DSL

class AppConfig
  def self.load_from_string(config_string)
    eval(config_string)
  end
end

config_string = "
  APP_NAME = 'MyApplication'
  DEBUG = true
  DATABASE = {
    host: 'localhost',
    port: 5432,
    name: 'myapp'
  }
"

AppConfig.load_from_string(config_string)
puts APP_NAME  # => "MyApplication"
puts DATABASE[:host]  # => "localhost"

Security Risks and Dangers

⚠️ CRITICAL WARNING: eval is extremely dangerous when used with untrusted input!

Dangerous Examples:

# NEVER do this with user input!
user_input = gets.chomp
eval(user_input)  # User could input: system('rm -rf /')

# Dangerous with file input
file_content = File.read('user_provided_file.rb')
eval(file_content)  # Could contain malicious code

Safe Alternatives:

# Instead of eval, use safer alternatives
class SafeCalculator
  def self.calculate(expression)
    # Use a math library or parser instead
    case expression
    when /^\d+\s*\+\s*\d+$/
      # Safe addition
      eval(expression)
    else
      raise "Unsafe expression"
    end
  end
end

Binding and Context

1. Custom Binding

def create_binding(variables = {})
  binding_obj = binding
  variables.each do |key, value|
    binding_obj.local_variable_set(key, value)
  end
  binding_obj
end

my_binding = create_binding(x: 10, y: 20)
result = eval("x + y", my_binding)
puts result  # => 30

2. Different Contexts

class ContextExample
  def initialize(value)
    @value = value
  end

  def evaluate_in_context(code)
    eval(code)
  end
end

obj = ContextExample.new("test")
result = obj.evaluate_in_context("@value")
puts result  # => "test"

Performance Considerations

  • eval is slower than direct code execution
  • The string must be parsed every time
  • No compile-time optimization
  • Use sparingly and only when necessary

Best Practices

1. Avoid When Possible

# Bad - using eval
method_name = "calculate"
eval("result = #{method_name}(5, 3)")

# Good - using send
result = send(method_name, 5, 3)

2. Whitelist Allowed Operations

class SafeEvaluator
  ALLOWED_OPERATIONS = %w[+ - * /]

  def self.evaluate(expression)
    operation = expression.match(/(\d+)\s*([+\-*/])\s*(\d+)/)
    return "Invalid expression" unless operation

    op = operation[2]
    return "Operation not allowed" unless ALLOWED_OPERATIONS.include?(op)

    eval(expression)
  end
end

3. Use Sandboxing

class SandboxedEval
  def self.safe_eval(code, timeout: 1)
    Timeout::timeout(timeout) do
      eval(code)
    end
  rescue Timeout::Error
    "Execution timed out"
  rescue => e
    "Error: #{e.message}"
  end
end

Real-World Usage

eval is used in:

  • Template engines (ERB, Haml)
  • Configuration systems (Rails initializers)
  • Code generators (Rails generators)
  • REPLs (Interactive Ruby shells)
# ERB template example
require 'erb'

template = ERB.new("Hello <%= name %>!")
name = "World"
result = template.result(binding)
puts result  # => "Hello World!"

Summary

eval is the most powerful metaprogramming tool in Ruby, but also the most dangerous. Use it only when:

  • You have complete control over the input
  • No safer alternative exists
  • You understand the security implications
  • You implement proper validation and sandboxing

For most use cases, prefer safer alternatives like send, method_missing, or dedicated parsing libraries.


✍️ 4. What is define_method?

Ruby’s define_method, which is a powerful meta-programming tool for dynamically creating methods at runtime.

define_method is a method that allows you to dynamically define instance methods on a class or module. It’s inherited from the Module class and is a key tool for meta-programming in Ruby.

Basic Syntax

class MyClass
  define_method :dynamic_method do |*args|
    puts "Hello from dynamic method with args: #{args}"
  end
end

obj = MyClass.new
obj.dynamic_method("test", 123)  # => "Hello from dynamic method with args: [\"test\", 123]"

Key Features

1. Dynamic Method Creation

class Calculator
  # Define methods dynamically based on operations
  [:add, :subtract, :multiply, :divide].each do |operation|
    define_method operation do |a, b|
      case operation
      when :add then a + b
      when :subtract then a - b
      when :multiply then a * b
      when :divide then a / b
      end
    end
  end
end

calc = Calculator.new
puts calc.add(5, 3)      # => 8
puts calc.multiply(4, 2)  # => 8
puts calc.divide(10, 2)   # => 5

2. Access to Instance Variables

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end

  # Create getter methods dynamically
  [:name, :age].each do |attribute|
    define_method attribute do
      instance_variable_get("@#{attribute}")
    end
  end
end

person = Person.new("Alice", 25)
puts person.name  # => "Alice"
puts person.age   # => 25

3. Method with Parameters

class DynamicAPI
  define_method :api_call do |endpoint, params = {}|
    puts "Calling #{endpoint} with params: #{params}"
    # Simulate API call
    "Response from #{endpoint}"
  end
end

api = DynamicAPI.new
api.api_call("/users", { id: 1 })  # => "Calling /users with params: {:id=>1}"

Common Use Cases

1. Attribute Accessors

class Model
  def self.attr_accessor(*attributes)
    attributes.each do |attribute|
      # Define getter
      define_method attribute do
        instance_variable_get("@#{attribute}")
      end

      # Define setter
      define_method "#{attribute}=" do |value|
        instance_variable_set("@#{attribute}", value)
      end
    end
  end

  attr_accessor :name, :email, :age
end

user = Model.new
user.name = "John"
user.email = "john@example.com"
puts user.name   # => "John"
puts user.email  # => "john@example.com"

2. Validation Methods

class User
  def initialize(attributes = {})
    attributes.each do |key, value|
      instance_variable_set("@#{key}", value)
    end
  end

  # Create validation methods dynamically
  [:name, :email, :age].each do |field|
    define_method "validate_#{field}" do
      value = instance_variable_get("@#{field}")
      case field
      when :name
        !value.nil? && !value.empty?
      when :email
        value =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
      when :age
        value.is_a?(Integer) && value > 0 && value < 150
      end
    end
  end
end

user = User.new(name: "Alice", email: "alice@example.com", age: 25)
puts user.validate_name   # => true
puts user.validate_email  # => true
puts user.validate_age    # => true

3. API Method Generation

class APIClient
  def initialize(base_url)
    @base_url = base_url
  end

  # Generate API methods for different resources
  [:users, :posts, :comments].each do |resource|
    define_method "get_#{resource}" do |id = nil|
      endpoint = id ? "#{resource}/#{id}" : resource
      puts "GET #{@base_url}/#{endpoint}"
      # Actual HTTP request would go here
    end

    define_method "create_#{resource.singularize}" do |data|
      puts "POST #{@base_url}/#{resource}"
      puts "Data: #{data}"
      # Actual HTTP request would go here
    end
  end
end

api = APIClient.new("https://api.example.com")
api.get_users           # => "GET https://api.example.com/users"
api.get_users(123)      # => "GET https://api.example.com/users/123"
api.create_user(name: "John")  # => "POST https://api.example.com/users"

4. Database Query Methods

class ActiveRecord
  def self.has_many(association_name)
    define_method association_name do
      puts "Fetching #{association_name} for #{self.class}"
      # Actual database query would go here
      []
    end
  end

  def self.belongs_to(association_name)
    define_method association_name do
      puts "Fetching #{association_name} for #{self.class}"
      # Actual database query would go here
      nil
    end
  end
end

class User < ActiveRecord
  has_many :posts
  belongs_to :company
end

user = User.new
user.posts    # => "Fetching posts for User"
user.company  # => "Fetching company for User"

Advanced Examples

1. Method with Dynamic Logic

class DynamicCalculator
  def self.create_operation(operation_name, &block)
    define_method operation_name do |*args|
      instance_eval(&block)
    end
  end

  create_operation :custom_add do
    args = method(__method__).parameters.map { |_, name| local_variable_get(name) }
    args.inject(:+)
  end

  create_operation :power do
    base, exponent = method(__method__).parameters.map { |_, name| local_variable_get(name) }
    base ** exponent
  end
end

calc = DynamicCalculator.new
puts calc.custom_add(1, 2, 3, 4)  # => 10
puts calc.power(2, 3)             # => 8

2. Conditional Method Definition

class FeatureToggle
  def self.define_feature_method(feature_name, enabled = true)
    if enabled
      define_method feature_name do
        puts "Feature #{feature_name} is enabled"
        # Feature implementation
      end
    else
      define_method feature_name do
        puts "Feature #{feature_name} is disabled"
        # Fallback or no-op
      end
    end
  end

  define_feature_method :new_ui, true
  define_feature_method :beta_feature, false
end

app = FeatureToggle.new
app.new_ui        # => "Feature new_ui is enabled"
app.beta_feature  # => "Feature beta_feature is disabled"

3. Method with Different Signatures

class FlexibleAPI
  def self.define_flexible_method(method_name)
    define_method method_name do |*args, **kwargs, &block|
      puts "Method: #{method_name}"
      puts "Arguments: #{args}"
      puts "Keyword arguments: #{kwargs}"
      puts "Block given: #{block_given?}"

      # Process based on arguments
      if block_given?
        block.call(args.first)
      elsif !kwargs.empty?
        kwargs.values.first
      else
        args.first
      end
    end
  end

  define_flexible_method :process
end

api = FlexibleAPI.new
api.process("hello")                    # => "hello"
api.process(data: "world")              # => "world"
api.process("test") { |x| x.upcase }    # => "TEST"

4. Method Aliasing

class MethodAliaser
  def self.create_aliases(base_method, *aliases)
    aliases.each do |alias_name|
      define_method alias_name do |*args, &block|
        send(base_method, *args, &block)
      end
    end
  end

  def original_method
    puts "Original method called"
  end

  create_aliases :original_method, :alias1, :alias2, :alternative_name
end

obj = MethodAliaser.new
obj.alias1              # => "Original method called"
obj.alternative_name    # => "Original method called"

Performance Considerations

1. Method Definition Timing

class PerformanceExample
  # Methods defined at class definition time (faster)
  define_method :static_method do
    puts "Static method"
  end

  def self.create_dynamic_methods
    # Methods defined at runtime (slower)
    1000.times do |i|
      define_method "dynamic_method_#{i}" do
        puts "Dynamic method #{i}"
      end
    end
  end
end

2. Memory Usage

class MemoryEfficient
  # More memory efficient - methods defined once
  METHODS = [:method1, :method2, :method3]

  METHODS.each do |method_name|
    define_method method_name do
      puts "Called #{method_name}"
    end
  end
end

Best Practices

1. Use Meaningful Names

# Good
define_method :calculate_total do
  # implementation
end

# Bad
define_method :m1 do
  # implementation
end

2. Handle Errors Gracefully

class SafeMethodDefiner
  def self.define_safe_method(method_name, &block)
    define_method method_name do |*args|
      begin
        instance_eval(&block)
      rescue => e
        puts "Error in #{method_name}: #{e.message}"
        nil
      end
    end
  end
end

3. Document Dynamic Methods

class DocumentedClass
  # Dynamically creates getter methods for attributes
  # @param attributes [Array<Symbol>] list of attribute names
  def self.create_getters(*attributes)
    attributes.each do |attr|
      define_method attr do
        instance_variable_get("@#{attr}")
      end
    end
  end

  create_getters :name, :email
end

Real-World Usage

define_method is commonly used in:

  • Rails ActiveRecord (associations, validations)
  • RSpec (dynamic test methods)
  • Configuration systems (dynamic setters/getters)
  • API clients (dynamic endpoint methods)
  • ORM frameworks (dynamic query methods)
# Rails-like example
class ActiveRecord
  def self.validates_presence_of(*attributes)
    attributes.each do |attribute|
      define_method "validate_#{attribute}_presence" do
        value = send(attribute)
        value.nil? || value.to_s.empty?
      end
    end
  end
end

class User < ActiveRecord
  validates_presence_of :name, :email
end

define_method is a powerful tool for creating flexible, dynamic APIs and reducing code duplication through meta-programming.


🌀 5. What is method_missing?

Ruby’s method_missing, which is a powerful meta-programming tool that allows you to handle calls to undefined methods dynamically.

method_missing is a special method in Ruby that gets called automatically when an object receives a message (method call) for a method that doesn’t exist. It’s inherited from the BasicObject class and is a key component of Ruby’s dynamic nature.

Basic Syntax

class MyClass
  def method_missing(method_name, *args, &block)
    puts "Method '#{method_name}' called with args: #{args}"
    # Handle the missing method call
  end
end

obj = MyClass.new
obj.undefined_method("hello", 123)  # => "Method 'undefined_method' called with args: [\"hello\", 123]"

Key Features

1. Automatic Invocation

class DynamicHandler
  def method_missing(method_name, *args, &block)
    puts "Trying to call: #{method_name}"
    puts "Arguments: #{args}"
    puts "Block given: #{block_given?}"

    # Return a default value or handle the call
    "Handled by method_missing"
  end
end

obj = DynamicHandler.new
result = obj.some_random_method("arg1", "arg2") { puts "block" }
puts result
# Output:
# Trying to call: some_random_method
# Arguments: ["arg1", "arg2"]
# Block given: true
# Handled by method_missing

2. Method Name and Arguments

class FlexibleAPI
  def method_missing(method_name, *args, **kwargs, &block)
    puts "Method: #{method_name}"
    puts "Arguments: #{args}"
    puts "Keyword arguments: #{kwargs}"

    # Handle different method patterns
    case method_name.to_s
    when /^get_(.+)$/
      "Getting #{$1}"
    when /^set_(.+)$/
      "Setting #{$1} to #{args.first}"
    else
      "Unknown method: #{method_name}"
    end
  end
end

api = FlexibleAPI.new
puts api.get_user_info     # => "Getting user_info"
puts api.set_name("Alice") # => "Setting name to Alice"
puts api.random_method     # => "Unknown method: random_method"

Common Use Cases

1. Dynamic Property Access

class DynamicProperties
  def initialize
    @data = {}
  end

  def method_missing(method_name, *args)
    method_str = method_name.to_s

    if method_str.end_with?('=')
      # Setter method
      property_name = method_str.chomp('=')
      @data[property_name] = args.first
    else
      # Getter method
      @data[method_str]
    end
  end
end

obj = DynamicProperties.new
obj.name = "Alice"
obj.age = 25
obj.city = "New York"

puts obj.name  # => "Alice"
puts obj.age   # => 25
puts obj.city  # => "New York"

2. API Method Generation

class APIClient
  def initialize(base_url)
    @base_url = base_url
  end

  def method_missing(method_name, *args)
    method_str = method_name.to_s

    case method_str
    when /^get_(.+)$/
      resource = $1
      puts "GET #{@base_url}/#{resource}"
      # Actual HTTP GET request
    when /^post_(.+)$/
      resource = $1
      data = args.first || {}
      puts "POST #{@base_url}/#{resource}"
      puts "Data: #{data}"
      # Actual HTTP POST request
    when /^put_(.+)$/
      resource = $1
      data = args.first || {}
      puts "PUT #{@base_url}/#{resource}"
      puts "Data: #{data}"
      # Actual HTTP PUT request
    when /^delete_(.+)$/
      resource = $1
      puts "DELETE #{@base_url}/#{resource}"
      # Actual HTTP DELETE request
    else
      super
    end
  end
end

api = APIClient.new("https://api.example.com")
api.get_users           # => "GET https://api.example.com/users"
api.post_user(name: "John")  # => "POST https://api.example.com/user"
api.put_user(1, name: "Jane")  # => "PUT https://api.example.com/user"
api.delete_user(1)      # => "DELETE https://api.example.com/user"

3. Configuration DSL

class Configuration
  def initialize
    @config = {}
  end

  def method_missing(method_name, *args)
    method_str = method_name.to_s

    if method_str.end_with?('=')
      # Setter
      key = method_str.chomp('=')
      @config[key] = args.first
    else
      # Getter
      @config[method_str]
    end
  end

  def to_hash
    @config
  end
end

config = Configuration.new
config.database_url = "postgresql://localhost/myapp"
config.api_key = ENV['API_KEY']
config.debug_mode = true

puts config.database_url  # => "postgresql://localhost/myapp"
puts config.to_hash       # => {"database_url"=>"postgresql://localhost/myapp", ...}

4. Builder Pattern

class HTMLBuilder
  def initialize
    @html = []
  end

  def method_missing(tag_name, *args, &block)
    content = args.first || ""
    attributes = args.last.is_a?(Hash) ? args.last : {}

    if block_given?
      @html << "<#{tag_name}#{format_attributes(attributes)}>"
      @html << yield
      @html << "</#{tag_name}>"
    else
      @html << "<#{tag_name}#{format_attributes(attributes)}>#{content}</#{tag_name}>"
    end

    self
  end

  private

  def format_attributes(attributes)
    return "" if attributes.empty?
    " " + attributes.map { |k, v| "#{k}=\"#{v}\"" }.join(" ")
  end

  def to_s
    @html.join("\n")
  end
end

builder = HTMLBuilder.new
html = builder.html do
  builder.head do
    builder.title("My Page")
  end
  builder.body(class: "main") do
    builder.h1("Hello World")
    builder.p("This is a paragraph", class: "intro")
  end
end

puts html
# Output:
# <html>
# <head>
# <title>My Page</title>
# </head>
# <body class="main">
# <h1>Hello World</h1>
# <p class="intro">This is a paragraph</p>
# </body>
# </html>

Advanced Examples

1. Method Caching with respond_to_missing?

class CachedMethodHandler
  def initialize
    @cache = {}
  end

  def method_missing(method_name, *args)
    method_str = method_name.to_s

    # Check if we should handle this method
    if method_str.start_with?('cached_')
      cache_key = "#{method_str}_#{args.hash}"

      if @cache.key?(cache_key)
        puts "Returning cached result for #{method_name}"
        @cache[cache_key]
      else
        puts "Computing result for #{method_name}"
        result = compute_result(method_str, args)
        @cache[cache_key] = result
        result
      end
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.start_with?('cached_') || super
  end

  private

  def compute_result(method_name, args)
    # Simulate expensive computation
    sleep(1)
    "Result for #{method_name} with #{args}"
  end
end

handler = CachedMethodHandler.new
puts handler.cached_expensive_calculation(1, 2, 3)  # Slow
puts handler.cached_expensive_calculation(1, 2, 3)  # Fast (cached)
puts handler.respond_to?(:cached_expensive_calculation)  # => true

2. Dynamic Delegation

class Delegator
  def initialize(target)
    @target = target
  end

  def method_missing(method_name, *args, &block)
    if @target.respond_to?(method_name)
      @target.send(method_name, *args, &block)
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    @target.respond_to?(method_name, include_private) || super
  end
end

class User
  def initialize(name, email)
    @name = name
    @email = email
  end

  def display_info
    "Name: #{@name}, Email: #{@email}"
  end
end

user = User.new("Alice", "alice@example.com")
delegator = Delegator.new(user)
puts delegator.display_info  # => "Name: Alice, Email: alice@example.com"

3. Method Chaining with method_missing

class QueryBuilder
  def initialize
    @conditions = []
    @order_by = nil
    @limit = nil
  end

  def method_missing(method_name, *args)
    method_str = method_name.to_s

    case method_str
    when /^where_(.+)$/
      field = $1
      value = args.first
      @conditions << "#{field} = '#{value}'"
      self
    when /^order_by_(.+)$/
      field = $1
      direction = args.first || 'ASC'
      @order_by = "#{field} #{direction}"
      self
    when /^limit$/
      @limit = args.first
      self
    when /^execute$/
      build_query
    else
      super
    end
  end

  private

  def build_query
    query = "SELECT * FROM table"
    query += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
    query += " ORDER BY #{@order_by}" if @order_by
    query += " LIMIT #{@limit}" if @limit
    query
  end
end

query = QueryBuilder.new
result = query.where_name("John")
              .where_age(25)
              .order_by_created_at("DESC")
              .limit(10)
              .execute

puts result
# => "SELECT * FROM table WHERE name = 'John' AND age = '25' ORDER BY created_at DESC LIMIT 10"

4. Event Handling

class EventHandler
  def initialize
    @handlers = {}
  end

  def method_missing(event_name, *args)
    method_str = event_name.to_s

    if method_str.end_with?('=')
      # Register event handler
      handler_name = method_str.chomp('=')
      @handlers[handler_name] = args.first
    else
      # Trigger event
      handler = @handlers[method_str]
      if handler
        handler.call(*args)
      else
        puts "No handler registered for event: #{method_str}"
      end
    end
  end
end

handler = EventHandler.new

# Register event handlers
handler.on_click = ->(x, y) { puts "Clicked at (#{x}, #{y})" }
handler.on_hover = ->(element) { puts "Hovered over #{element}" }

# Trigger events
handler.on_click(100, 200)  # => "Clicked at (100, 200)"
handler.on_hover("button")  # => "Hovered over button"
handler.on_keypress         # => "No handler registered for event: on_keypress"

Important Considerations

1. respond_to_missing?

Always implement respond_to_missing? when using method_missing:

class ProperHandler
  def method_missing(method_name, *args)
    if method_name.to_s.start_with?('dynamic_')
      "Handled: #{method_name}"
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.start_with?('dynamic_') || super
  end
end

obj = ProperHandler.new
puts obj.respond_to?(:dynamic_method)  # => true
puts obj.respond_to?(:real_method)     # => false

2. Performance Impact

class PerformanceExample
  def method_missing(method_name, *args)
    # This gets called for EVERY undefined method
    # Can be slow if called frequently
    puts "Handling #{method_name}"
  end
end

# Better approach: Define methods when first called
class BetterExample
  def method_missing(method_name, *args)
    if method_name.to_s.start_with?('dynamic_')
      # Define the method for future calls
      self.class.define_method(method_name) do |*method_args|
        "Handled: #{method_name} with #{method_args}"
      end

      # Call the newly defined method
      send(method_name, *args)
    else
      super
    end
  end
end

3. Debugging

class DebuggableHandler
  def method_missing(method_name, *args, &block)
    puts "DEBUG: method_missing called for #{method_name}"
    puts "DEBUG: arguments: #{args}"
    puts "DEBUG: block given: #{block_given?}"

    # Your handling logic here
    super
  end
end

Real-World Usage

method_missing is commonly used in:

  • Rails ActiveRecord (dynamic finders)
  • RSpec (dynamic matchers)
  • Sinatra (dynamic route handlers)
  • Configuration systems (dynamic setters/getters)
  • API clients (dynamic endpoint methods)
# Rails-like example
class ActiveRecord
  def method_missing(method_name, *args)
    method_str = method_name.to_s

    if method_str.start_with?('find_by_')
      field = method_str.sub('find_by_', '')
      value = args.first
      puts "Finding record where #{field} = #{value}"
      # Actual database query
    else
      super
    end
  end
end

class User < ActiveRecord
end

User.find_by_name("Alice")  # => "Finding record where name = Alice"
User.find_by_email("alice@example.com")  # => "Finding record where email = alice@example.com"

method_missing is a powerful tool for creating flexible, dynamic APIs, but should be used carefully with proper implementation of respond_to_missing? and consideration for performance.


🏷️ Part 2: Other Metaprogramming Tools

Ruby provides many more hooks for dynamic behavior:

🔗 1. define_singleton_method

Creates a method on a single object, similar to instance_eval + def.

str = "hello"
def str.shout; upcase + '!'; end
# vs.
str.define_singleton_method(:whisper) { downcase + '...' }

📦 2. module_eval / module_exec

Like class_eval but in a module’s context, useful to mix in behavior.

module Mixin; end
Mixin.module_eval do
  def helper; "I'm mixed in"; end
end

🧩 3. send / public_send

Invoke methods by name, bypassing visibility with send, or respecting it with public_send.

obj.send(:private_method)
obj.public_send(:public_method)

Both send and public_send in Ruby allow you to dynamically call methods on an object by name (as a symbol or string).
But the difference lies in method visibility (public, private, protected).

Key Difference

MethodCan call private/protected methods?Example Usage
sendYes"hello".send(:upcase)
public_sendNo (only public methods)"hello".public_send(:upcase)

Example

class MyClass
  def public_method
    "I'm public"
  end

  private
  def secret_method
    "I'm private"
  end
end

obj = MyClass.new

puts obj.send(:public_method)      # ✅ Works: "I'm public"
puts obj.public_send(:public_method) # ✅ Works: "I'm public"

puts obj.send(:secret_method)      # ✅ Works (can access private method)
puts obj.public_send(:secret_method) # ❌ Raises NoMethodError

? Why this matters

  • send is very powerful (and dangerous) because it ignores method visibility. It’s often used in metaprogramming (e.g., ActiveRecord uses it internally).
  • public_send is safer because it respects encapsulation. Use this when you don’t want to accidentally call private methods.

Best Practice

  • Use public_send whenever possible (especially if the method name comes from user input) to avoid security issues.
  • Use send only when you explicitly need access to private methods (e.g., in testing or metaprogramming).

🏷️ 4. Constant Manipulation (const_get, const_set)

Dynamically read or write constants on classes or modules.

Object.const_set('DynamicClass', Class.new)
klass = Object.const_get('DynamicClass')

🛠️ 5. Alias & Removal (alias_method, undef_method, remove_method)

Alias existing methods or remove definitions at runtime.

def foo; :bar; end
alias_method :baz, :foo
undef_method :foo

📥 6. Instance Variable Access (instance_variable_get, instance_variable_set)

Read or write any object’s internal state.

obj.instance_variable_set(:@data, 123)
puts obj.instance_variable_get(:@data) # => 123

🔮 7. Eigenclass & class << obj

Open the singleton class to define methods or mixins.

class << obj
  def special; 'only me'; end
end

📡 8. autoload / require_relative

Delay loading of modules until first reference.

autoload :Parser, 'parser'

📐 9. respond_to_missing?

Complement method_missing to accurately report capabilities.

def respond_to_missing?(m, include_private=false)
  @target.respond_to?(m) || super
end

🔀 10. Refinements

Scoped monkey-patching without global impact.

module StringExtensions
  refine String do
    def shout; upcase; end
  end
end

using StringExtensions
puts 'hi'.shout # => HI

✅ Conclusion

Ruby’s metaprogramming toolbox is vast—from the core five methods in Part 1 to the advanced techniques in Part 2. By mastering these, you can write highly dynamic, DRY, and expressive code, but always balance power with clarity and maintainability.

Happy Ruby coding! 🚀


🔍 1. Why Use class_eval and instance_eval?

Ruby classes and objects are open, meaning you can modify them at runtime.

  • class_eval lets you inject instance methods into a class after it’s defined.
  • instance_eval lets you add singleton methods or access private state on a single object.

✨ Dynamic Method Generation

When you don’t know ahead of time what methods you’ll need—say you’re reading an external schema or configuration—you can’t hand‑write every method. Metaprogramming with class_eval/instance_eval generates them on the fly.

🔧 Building DSLs

Internal DSLs (e.g. Rails’ routing or configuration blocks) rely on evaluating blocks in the right context:

# routes.rb
Rails.application.routes.draw do
  resources :users do
    resources :posts
  end
end

Here, instance_eval on the routing object makes resources available inside the block.

If you know all your methods at design time, define them normally. Lean on metaprogramming when you need flexibility, DRY generation, or want to craft a clean DSL.


🛠️ 2. When to Use define_method?

define_method is Ruby’s block‑based way to dynamically add methods in a class or module—safer than eval and can close over local variables:

class Serializer
  %i[name age email].each do |attr|
    define_method("serialize_#{attr}") do |user|
      user.public_send(attr).to_s
    end
  end
end

  • Ideal for generating many similar methods (attribute serializers, dynamic finders).
  • Keeps code DRY without string interpolation.

📦 3. Ruby’s *arg and **arg Operators

⭐ Single Splat: *args

  • Definition: Packs extra positional args into an array, or unpacks an array into individual arguments. def foo(a, *others) p a # first arg p others # rest as an array end foo(1, 2, 3) # a=1, others=[2,3] foo(*[4,5,6]) # same as foo(4,5,6)

✨ Double Splat: **kwargs

  • Definition: Packs extra keyword args into a hash, or unpacks a hash into keyword arguments. def bar(x:, **opts) p x # required keyword p opts # other keywords in a hash end bar(x: 10, y: 20, z: 30) # opts={y:20, z:30} bar(**{x:1, y:2}) # same as bar(x:1, y:2)

📚 4. What Is a DSL? Main Ruby DSLs in Rails

A DSL (Domain‑Specific Language) is an internal API that reads like its own language, tailored to a task. Rails ships with many:

🚦 Routing DSL

Rails.application.routes.draw do
  resources :users
end

🏗️ ActiveRecord Query DSL

User.where(active: true)
    .order(created_at: :desc)
    .limit(10)

🗄️ Migrations DSL

class CreateProducts < ActiveRecord::Migration[8.0]
  def change
    create_table :products do |t|
      t.string :name
      t.decimal :price
    end
  end
end

💬 Validation DSL

class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true
end

🔄 Callback DSL

class Order < ApplicationRecord
  before_save :calculate_total
  after_commit :send_confirmation_email
end

✔️ These meta-programming techniques and splat operators power Ruby’s flexibility—and underpin the expressive DSLs that make Rails code so readable.


Enjoy Ruby! 🚀

Unknown's avatar

Author: Abhilash

Hi, I’m Abhilash! A seasoned web developer with 13+ years of experience specializing in Ruby and Ruby on Rails. Since 2010, I’ve built scalable, robust web applications and worked with frameworks like Angular, Sinatra, Laravel, Node.js, and React. Passionate about clean, maintainable code and continuous learning, I share insights, tutorials, and experiences here. Let’s explore the ever-evolving world of web development together!

Leave a comment