method_missing and respond_to_missing? — Ruby Metaprogramming That's Actually Useful
Metaprogramming has a reputation for being either magical or dangerous, depending on who you ask. Both reactions are earned. Ruby’s method_missing hook lets you intercept calls to methods that don’t exist and handle them dynamically — which is powerful when used carefully and a debugging nightmare when abused. The trick is knowing what it’s actually good for, implementing it correctly alongside respond_to_missing?, and resisting the urge to reach for it when a plain method would do.
How method_missing Works
When you call a method on an object and Ruby can’t find it in the method lookup chain — the object’s class, its ancestors, its mixins — it calls method_missing as a last resort. By default, method_missing raises NoMethodError. Override it, and you can intercept that call and do something useful.
Example:
class FlexibleLogger
def method_missing(name, *args, **kwargs, &block)
if name.to_s.start_with?("log_")
level = name.to_s.sub("log_", "")
puts "[#{level.upcase}] #{args.first}"
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?("log_") || super
end
end
logger = FlexibleLogger.new
logger.log_info("Server started") # => [INFO] Server started
logger.log_warning("Disk space low") # => [WARNING] Disk space low
logger.log_error("DB connection lost") # => [ERROR] DB connection lost
method_missing catches log_info, log_warning, and log_error — none of which are defined explicitly. The pattern check routes to the logging logic; anything else calls super, which propagates the NoMethodError normally.
The Critical Partner: respond_to_missing?
Here’s where most method_missing implementations go wrong: they override method_missing but forget respond_to_missing?. The result is an object that handles dynamic method calls but lies about its capabilities.
Example:
logger = FlexibleLogger.new
logger.respond_to?(:log_info) # => false (wrong — it DOES respond)
logger.method(:log_info) # => NameError
respond_to? checks respond_to_missing? for methods not defined normally. If you don’t override it, Ruby assumes those methods don’t exist. Code that checks respond_to? before calling (a common defensive pattern) breaks silently.
The fix is always to pair method_missing with respond_to_missing?:
Example:
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?("log_") || super
end
logger.respond_to?(:log_info) # => true
logger.method(:log_info) # works
Rule: If you implement method_missing, always implement respond_to_missing? with matching logic.
A More Real-World Pattern: Dynamic Finders
ActiveRecord uses method_missing internally to power find_by_name, find_by_email, and similar dynamic finders (in older Rails versions). Here’s a simplified version that illustrates the pattern:
Example:
class UserRepository
USERS = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
{ id: 3, name: "Carol", role: "admin" }
]
def method_missing(name, *args, **kwargs, &block)
match = name.to_s.match(/\Afind_by_(\w+)\z/)
if match
attribute = match[1].to_sym
USERS.find { |u| u[attribute] == args.first }
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.match?(/\Afind_by_\w+\z/) || super
end
end
repo = UserRepository.new
repo.find_by_name("Alice") # => { id: 1, name: "Alice", role: "admin" }
repo.find_by_role("admin") # => { id: 1, name: "Alice", role: "admin" }
One method_missing implementation handles find_by_anything dynamically. Adding a new attribute to search by requires zero code changes.
define_method as the Better Alternative
For many use cases where method_missing feels tempting, define_method is cleaner. Instead of intercepting every unknown call, you generate the methods explicitly at class definition time:
Example:
class FlexibleLogger
%w[info warning error debug].each do |level|
define_method("log_#{level}") do |message|
puts "[#{level.upcase}] #{message}"
end
end
end
logger = FlexibleLogger.new
logger.log_info("Server started")
logger.respond_to?(:log_info) # => true (no method_missing needed)
logger.method(:log_info) # => works normally
define_method creates real methods at load time. They show up in respond_to?, method, and introspection tools. They appear in backtraces. They’re faster at runtime — no dynamic dispatch overhead.
Use define_method when the set of methods is known at class definition time. Use method_missing when the method names are truly dynamic and unknowable in advance.
Pro-Tip:
method_missingis expensive. Every call that hits it goes through Ruby’s full method lookup chain first. If you’re calling dynamic methods in a tight loop, the overhead adds up. Profile before assuming it’s fast enough. And in anymethod_missingimplementation, always callsuperin the else branch — not raisingNoMethodErroryourself — so that othermethod_missingimplementations in the ancestor chain still work correctly.
When Not to Use method_missing
The temptation is to reach for method_missing anytime you want “magic” behavior. Resist it. The debugging experience is poor — stack traces are harder to read, tools like binding.pry and language servers can’t autocomplete or navigate to dynamic methods, and the failure mode when something goes wrong is often confusing.
Good uses: DSLs that accept arbitrary method names (like RSpec matchers), proxy objects that forward calls to another object, adapters that map methods to external APIs with varying names.
Bad uses: avoiding writing repetitive but straightforward methods, “just to be clever,” anything where a hash lookup or define_method would work just as well.
Conclusion
method_missing is one of Ruby’s genuinely powerful features — and one of its most misused. Used correctly, with respond_to_missing? always alongside it and super always in the fallback, it enables DSLs and adapters that would otherwise require significant boilerplate. Used carelessly, it creates objects that lie about their interface and code that’s hard to debug. Know the tool, know the alternative (define_method), and reach for whichever one makes the code clearer.
FAQs
Q1: Does method_missing work with keyword arguments in Ruby 3?
Yes. Include **kwargs in the signature: def method_missing(name, *args, **kwargs, &block). In Ruby 3, positional and keyword arguments are strictly separated, so the double-splat is necessary to correctly capture keyword arguments passed to the missing method.
Q2: Can method_missing slow down my application?
Yes, measurably in tight loops. Ruby traverses the entire ancestor chain before calling method_missing. For rarely-called code paths it’s negligible; for hot paths, benchmark and consider define_method instead.
Q3: How do I see which methods method_missing is intercepting?
Add a puts or Rails.logger.debug inside your method_missing with the name argument during development. Or use set_trace_func / TracePoint to trace method calls. The name argument is always the Symbol of the missing method name.
Q4: What’s the difference between method_missing and BasicObject#method_missing?
BasicObject is Ruby’s root object with almost no methods. BasicObject#method_missing is the ultimate fallback — it raises NoMethodError. When you call super from your method_missing, the call eventually reaches this. Blank slate objects (proxy objects that forward everything) sometimes inherit from BasicObject to avoid inheriting Object’s methods.
Q5: Can I use method_missing in a module that’s included into other classes?
Yes. Modules can define method_missing and respond_to_missing?. When included, these hook into the host class’s method lookup. Be careful about specificity in your pattern matching — a module’s method_missing shouldn’t swallow calls meant for the host class or other mixins.
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀