rubyguides

BasicObject#method_missing

method_missing(method_name, *args, &block)

method_missing is a hook that Ruby calls whenever you invoke a method that doesn’t exist on an object. By default it raises NoMethodError, but you can override it to handle missing methods dynamically. This is the foundation of many Ruby metaprogramming patterns: dynamic delegation, fluent interfaces, and attribute shortcuts.

When It’s Called

Every time you call a method Ruby can’t find:

obj = Object.new
obj.some_undefined_method  # => NoMethodError: undefined method `some_undefined_method'

Override method_missing to intercept these calls instead:

class Product
  def method_missing(method_name, *args, &block)
    if method_name.to_s.start_with?('price_')
      currency = method_name.to_s.sub('price_', '')
      # fetch price in that currency
      "Price in #{currency}"
    else
      super  # fall through to default behaviour
    end
  end
end

p = Product.new
p.price_usd  # => "Price in usd"
p.price_eur  # => "Price in eur"

Signature

def method_missing(method_name, *args, &block)
ArgumentDescription
method_nameA symbol (or string in older Ruby) for the missing method name
*argsArguments passed to the call
&blockBlock passed to the call (if any)

Dynamic Attribute Access

A common pattern: attribute_name returns the value of @attribute_name:

class User
  def initialize(attrs = {})
    attrs.each { |k, v| instance_variable_set("@#{k}", v) }
  end

  def method_missing(method_name, *args, &block)
    if method_name.to_s.end_with?('?')
      !!instance_variable_get("@#{method_name.to_s.chomp('?')}")
    elsif method_name.to_s.end_with?('=')
      instance_variable_set("@#{method_name.to_s.chomp('=')}", args.first)
    else
      instance_variable_get("@#{method_name}")
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.end_with?('?') ||
    method_name.to_s.end_with?('=') ||
    true
  end
end

user = User.new(name: 'Ada', email: 'ada@example.com')
user.name    # => "Ada"
user.email   # => "ada@example.com"
user.admin?  # => false
user.name = 'Louise'
user.name    # => "Louise"

Delegation

Forward calls to another object transparently:

class Interface
  def initialize(backend)
    @backend = backend
  end

  def method_missing(method_name, *args, &block)
    @backend.public_send(method_name, *args, &block)
  end

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

class Calculator
  def add(a, b) a + b end
  def multiply(a, b) a * b end
end

calc = Interface.new(Calculator.new)
calc.add(2, 3)        # => 5
calc.multiply(4, 5)   # => 20
calc.respond_to?(:add) # => true

This is a simplified version of Forwardable — useful when you need custom delegation logic.

Fluent Interfaces

Build chainable method calls:

class QueryBuilder
  def initialize
    @conditions = []
  end

  def method_missing(method_name, *args, &block)
    @conditions << [method_name, args.first]
    self  # return self for chaining
  end

  def to_sql
    @conditions.map { |method, value| "#{method} = #{value}" }.join(' AND ')
  end
end

query = QueryBuilder.new
sql = query.where_age.greater_than(18).where_status.equal('active').to_sql
# => "where_age = greater_than AND where_status = equal"  (naive — customize as needed)

respond_to_missing?

When you override method_missing, your object may lie about what it responds to by default. Add respond_to_missing? to fix that:

class ActiveRecord
  def method_missing(method_name, *args, &block)
    # handle dynamic finders like find_by_email
    if method_name.to_s.start_with?('find_by_')
      attr = method_name.to_s.sub('find_by_', '')
      # find record by attribute
      "Finding by #{attr}"
    else
      super
    end
  end

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

record = ActiveRecord.new
record.respond_to?(:find_by_email)  # => true
record.respond_to?(:save)            # => true (from super)

Key Rule: Always Call super

If you don’t handle a method, call super so Ruby can raise NoMethodError:

def method_missing(method_name, *args, &block)
  if method_name == :my_custom_method
    process(*args)
  else
    super  # let Ruby raise NoMethodError
  end
end

Forgetting super silently swallows all undefined methods, making debugging very difficult.

Caveats

  • Performance: method_missing is slower than defined methods. If a method is called frequently, define it properly instead.
  • Debugging: hidden dispatch makes stack traces less obvious. Document the methods you’re intercepting.
  • Name collisions: method_missing catches everything. Use respond_to_missing? to properly report what’s available.
  • Argument errors: if you intercept the call but the arguments are wrong, you’ll get a ArgumentError from inside method_missing — not a helpful message about the original call.

See Also