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)
| Argument | Description |
|---|---|
method_name | A symbol (or string in older Ruby) for the missing method name |
*args | Arguments passed to the call |
&block | Block 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_missingis 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_missingcatches everything. Userespond_to_missing?to properly report what’s available. - Argument errors: if you intercept the call but the arguments are wrong, you’ll get a
ArgumentErrorfrom insidemethod_missing— not a helpful message about the original call.
See Also
- /guides/ruby-metaprogramming-basics/ — method_missing in the broader context of Ruby’s metaprogramming toolkit
- /guides/ruby-blocks-procs-lambdas/ — using Forwardable for delegation without writing method_missing by hand
- /reference/kernel-methods/abort// — checking which methods an object responds to