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.

That flexibility is powerful, but it comes with a cost: the code becomes harder to discover unless you also teach Ruby how to report the behavior correctly. In practice, method_missing works best when the dynamic rule is simple enough to explain in one sentence.

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'

That first example shows the default Ruby behavior: if the method truly does not exist, Ruby raises immediately. The override in the next block changes only that missing-method path, so you can see exactly where the dynamic behavior starts.

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"

This pattern is easy to read because the missing method name itself carries the data. The object is effectively treating the method name as part of the request, which is why method_missing can feel more like a mini parser than a normal dispatch hook.

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"

Dynamic attribute handling like this can be convenient in small DSLs, but it is also easy to make too magical. If the method names are predictable, defining real methods is usually easier to understand and document.

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.

Delegation is one of the cleanest uses for method_missing because the rule can stay narrow: if the backend knows the method, forward it; otherwise, raise the normal error. That keeps the object mostly transparent while still letting you customize the handoff.

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)

This example shows the main risk of fluent method_missing APIs: they can become hard to validate if the method names are doing all the work. A real implementation usually needs a more explicit mapping step so the output stays meaningful.

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)

The extra hook is important because it keeps introspection honest. Without it, code that checks respond_to? would miss the dynamic behavior, and the object would appear less capable than it really is.

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.

That warning is the real reason method_missing needs discipline. If the method is truly meant to handle a name, do the work and return. If not, let Ruby raise the error the caller expects.

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.

Those caveats are why method_missing is best used for narrow, intentional metaprogramming, not as a default replacement for normal methods. When the API is stable, explicit methods are easier to read and test.

See Also