Metaprogramming Basics in Ruby

· 4 min read · Updated March 7, 2026 · intermediate
metaprogramming ruby dynamic runtime

Metaprogramming is one of Ruby’s most powerful features, allowing you to write code that writes code. Instead of explicitly defining every method, class, or module, metaprogramming lets you create these dynamically at runtime. This guide covers the fundamental techniques that form the backbone of advanced Ruby programming.

What is Metaprogramming?

Metaprogramming refers to techniques that treat programs as data—code that can be inspected, modified, and generated while the program runs. Ruby provides several built-in mechanisms for this:

  • define_method — Create methods dynamically
  • method_missing — Handle calls to undefined methods
  • send — Invoke methods by name at runtime
  • eval — Execute strings as code

These tools are used extensively in popular gems like Rails, RSpec, and ActiveRecord.

Dynamic Methods with define_method

The define_method lets you create methods programmatically. Instead of writing each method explicitly, you can generate them based on data or configuration.

class Calculator
  %w[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 b.zero? ? nil : a / b
      end
    end
  end
end

calc = Calculator.new
calc.add(10, 5)        # => 15
calc.multiply(3, 4)    # => 12

This pattern is common in Rails, where methods like has_many and validates generate dozens of methods based on configuration.

Handling Missing Methods with method_missing

When you call a method that doesn’t exist, Ruby normally raises a NoMethodError. But you can intercept these calls using method_missing. This is how Rails’ dynamic finders work—find_by_email doesn’t exist until you call it.

class DynamicFinder
  def method_missing(method_name, *args, &block)
    if method_name.to_s.start_with?("find_by_")
      attribute = method_name.to_s.sub("find_by_", "")
      find_by_attribute(attribute, args.first)
    else
      super
    end
  end

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

  private

  def find_by_attribute(attr, value)
    puts "Searching by #{attr} for #{value}"
    # Simulated database lookup
    "Found record #{attr}: #{value}"
  end
end

finder = DynamicFinder.new
finder.find_by_email("user@example.com")
finder.find_by_username("johndoe")

The respond_to_missing? method ensures the dynamic methods are reported correctly when something checks what methods an object responds to.

Sending Messages with send

The send method lets you call any method by its name as a string or symbol. This is useful for invoking methods programmatically, especially when building frameworks or testing tools.

class Speaker
  def hello
    puts "Hello!"
  end

  def goodbye
    puts "Goodbye!"
  end

  def greet(name)
    puts "Hello, #{name}!"
  end
end

speaker = Speaker.new
speaker.send(:hello)              # => Hello!
speaker.send("goodbye")            # => Goodbye!
speaker.send(:greet, "Alice")     # => Hello, Alice!

# Useful for calling methods from configuration
actions = [:hello, :goodbye, :greet]
actions.each { |action| speaker.send(action) }

send bypasses privacy checks. Use public_send if you need to respect visibility.

Evaluating Code with eval

For complete runtime code generation, Ruby’s eval executes a string as Ruby code. Use it sparingly—it’s powerful but can be dangerous and hard to debug.

# Simple eval example
result = eval("10 + 5")
puts result  # => 15

# Dynamic method definition
class Example
  eval <<-RUBY
    def dynamic_method
      "I was created at runtime!"
    end
  RUBY
end

e = Example.new
puts e.dynamic_method  # => I was created at runtime!

For safer alternatives, consider instance_eval, class_eval, and module_eval (also available as module_exec and class_exec).

When to Use Metaprogramming

Metaprogramming shines when building:

  • Domain-specific languages (DSLs)
  • Frameworks that generate code based on configuration
  • APIs that provide convenience methods dynamically
  • Testing libraries with expressive syntax

Avoid metaprogramming when simple, explicit code would work. The flexibility comes at the cost of readability and debugging difficulty.

When Not to Use Metaprogramming

Don’t use metaprogramming for:

  • Simple cases that can be solved with plain methods
  • Code that others will maintain without understanding the dynamic behavior
  • Performance-critical sections where method lookup overhead matters
  • Security-sensitive code where eval could introduce vulnerabilities

If you find yourself writing complex metaprogramming to solve a simple problem, step back and consider a more straightforward approach.

Summary

Ruby’s metaprogramming capabilities give you extraordinary flexibility:

  • define_method creates methods programmatically
  • method_missing intercepts calls to undefined methods
  • send invokes methods by name at runtime
  • eval executes arbitrary code strings

Use these tools judiciously. The best metaprogramming is invisible—users shouldn’t need to understand the dynamic underpinnings to use your code effectively.