Metaprogramming with method_missing and define_method

Sarah Mitchell Feb 2026
3 tabs
class DynamicFinder
  def initialize(records)
    @records = records
  end

  def method_missing(method_name, *args, &block)
    if method_name.to_s =~ /^find_by_(.+)$/
      attribute = $1
      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(attribute, value)
    @records.find { |record| record[attribute.to_sym] == value }
  end
end

# Usage:
users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
]

finder = DynamicFinder.new(users)
finder.find_by_name('Alice')   # => { id: 1, name: 'Alice', ... }
finder.find_by_email('bob@example.com')  # => { id: 2, ... }
3 files · ruby Explain with highlit

Ruby's metaprogramming enables dynamic method definition and interception. method_missing catches undefined method calls, allowing DSL creation and proxy patterns. I implement it carefully with respond_to_missing? for proper introspection. define_method creates methods dynamically at runtime, useful for reducing repetition. Class and instance variables can be manipulated via class_variable_set and instance_variable_set. send and public_send invoke methods dynamically. Metaprogramming powers Rails' magic—associations, validations, callbacks. I use it judiciously to create expressive APIs without sacrificing clarity. Understanding Ruby's object model—classes are objects, methods are objects—unlocks powerful patterns. Metaprogramming reduces boilerplate while maintaining Ruby's elegance and readability.