method_missing and respond_to_missing?
method_missing is one of Ruby’s most powerful metaprogramming tools. It lets you intercept calls to methods that don’t exist, enabling dynamic interfaces, DSLs, and flexible message handling. But with great power comes responsibility—respond_to_missing? is the companion method that keeps your objects honest.
How method_missing Works
When you call a method that doesn’t exist on an object, Ruby normally raises a NoMethodError. However, if the object defines method_missing, that gets called instead, giving you a chance to handle the unknown method dynamically.
class DynamicRecorder
def method_missing(method_name, *args, &block)
puts "Called: #{method_name} with #{args.inspect}"
# You can store these calls, forward them, or do anything else
end
end
recorder = DynamicRecorder.new
recorder.foo(1, 2, 3) # => Called: foo with [1, 2, 3]
recorder.bar # => Called: bar with []
The first argument is the method name as a symbol, followed by any arguments, and optionally a block.
Use Cases
1. Building DSLs
method_missing shines when creating domain-specific languages:
class HtmlBuilder
def method_missing(tag_name, *args, &block)
content = block ? block.call : ""
attrs = args.first || {}
attr_string = attrs.map { |k, v| "#{k}='#{v}'" }.join(" ")
"<#{tag_name} #{attr_string}>#{content}</#{tag_name}>"
end
end
builder = HtmlBuilder.new
puts builder.div(class: "container") do
builder.p("Hello, world!")
end
# => <div class='container'><p>Hello, world!</p></div>
2. Forwarding to Other Objects
You can use method_missing to delegate method calls:
class User
attr_reader :name, :email
def initialize(name, email)
@name = name
@email = email
end
end
class UserPresenter
def initialize(user)
@user = user
end
def method_missing(method, *args, &block)
@user.send(method, *args, &block)
end
end
user = User.new("Alice", "alice@example.com")
presenter = UserPresenter.new(user)
puts presenter.name # => Alice
puts presenter.email # => alice@example.com
3. ActiveRecord-Like Associations
Rails uses method_missing to enable methods like user.posts without explicitly defining them:
class Post
def self.find_by_author(author)
# Simulated query
["Post by #{author}"]
end
end
class AuthorProxy
def method_missing(method_name, *args, &block)
if method_name.to_s.end_with?("s")
# Assume plural method names are collections (e.g., posts)
model = method_name.to_s.singularize.capitalize
puts "Looking up #{model} records..."
Object.const_get(model).find_by_author(args.first)
else
super
end
end
end
Why respond_to_missing? Matters
When you override method_missing, the default respond_to? still returns false for methods you’re handling:
class Greeter
def method_missing(name, *args)
"Hello, #{name}!"
end
end
greeter = Greeter.new
greeter.alice # => "Hello, alice!"
greeter.respond_to?(:alice) # => false - WRONG!
This breaks the principle of least surprise. Users expect respond_to? to match method_missing behavior. Override respond_to_missing? to fix this:
class Greeter
def method_missing(name, *args)
if name.to_s =~ /\A[a-z_]+\z/
"Hello, #{name}!"
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s =~ /\A[a-z_]+\z/ || super
end
end
greeter = Greeter.new
greeter.alice # => "Hello, alice!"
greeter.respond_to?(:alice) # => true - CORRECT!
The include_private parameter defaults to false. Set it to true if you’re also handling private method calls via method_missing.
Performance Considerations
Every call to an undefined method goes through method_missing, which is slower than calling a defined method directly. If you’re building performance-critical code:
# Instead of this:
def method_missing(method, *args)
send(method, *args) # Can cause infinite recursion!
end
# Define respond_to_missing? to avoid infinite loops:
def respond_to_missing?(method, include_private = false)
true
end
Common Gotchas
Infinite Recursion
Avoid calling the method inside method_missing without a guard:
# BAD - causes infinite recursion
def method_missing(method, *args)
send(method, *args) # Calls itself forever!
end
swallow_errors Silently
Don’t silently swallow all errors—it hides bugs:
# BAD practice
def method_missing(*)
# Swallowing everything hides real bugs
end
Always Call super
When you can’t handle a method, call super to preserve normal error behavior:
def method_missing(method_name, *args, &block)
if can_handle?(method_name)
handle(method_name, *args, &block)
else
super # Preserves NoMethodError for unhandled methods
end
end
Best Practices
- Always define
respond_to_missing?when you overridemethod_missing - Be specific about which methods you handle (use guards/patterns)
- Call
superfor methods you don’t handle - Document what dynamic methods your class supports
- Consider
define_methodfor frequently-called methods as an optimization
Summary
method_missing enables flexible, dynamic interfaces in Ruby—from DSLs to delegation to Rails-like associations. But with great power comes the responsibility to implement respond_to_missing?, keeping your objects’ interface consistent and predictable. Together, these methods let you build expressive APIs while maintaining Ruby’s duck typing guarantees.
Mastering these methods opens up advanced metaprogramming patterns. You’ll be able to create fluent interfaces that feel native to Ruby, build internal DSLs that read like plain English, and implement delegation patterns that would otherwise require significant boilerplate. The key is restraint: use these tools when they genuinely simplify your code, not just because they’re available.
Remember that every abstraction has a cost. Dynamic method resolution makes debugging harder, complicates static analysis, and can mask naming typos that would otherwise be caught at compile time. Use method_missing for genuine flexibility, but prefer explicit methods when the interface is stable and well-understood.
See Also
- ruby-blocks-procs-lambdas — Understanding Ruby’s blocks, procs, and lambdas
- ruby-metaprogramming-basics — Core metaprogramming concepts in Ruby
- ruby-open-classes — Opening and modifying classes at runtime