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)
| 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"
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_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.
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
- /guides/ruby-metaprogramming-basics/ — method_missing in the broader context of Ruby’s metaprogramming toolkit
- /reference/core-classes/respond-to/ — checking which methods an object responds to
- /reference/core-classes/send-method/ — dynamically calling methods by name