Mastering method_missing in Ruby
What is method_missing?
Every time you call a method on a Ruby object, Ruby searches the object’s method table. When it doesn’t find the method, instead of immediately raising NoMethodError, Ruby calls a special hook: method_missing. This gives you a chance to handle arbitrary method calls dynamically.
That’s the core of Ruby’s open metaprogramming model; and it’s the same mechanism that powers Rails, RSpec, Sinatra, and dozens of other libraries you have probably used without realizing it.
Intro context
Mastering method_missing in Ruby is easiest when you think of it as a fallback hook. Ruby only reaches it after the normal lookup path fails, which means the method name, arguments, and block all arrive at one place for you to interpret. That makes it useful for DSLs, delegation, and dynamic lookup code, but it also means the class can become harder to read if you use it everywhere. This guide focuses on the cases where the flexibility is worth the tradeoff.
If you want the official language background while you read, the Ruby docs for Object#method_missing describe the hook at the API level. The examples below show how to make the behavior predictable instead of magical.
TL;DR
- Use
method_missingwhen the method names are genuinely dynamic. - Pair it with
respond_to_missing?sorespond_to?stays honest. - Prefer
superor an explicit error for names you do not handle. - Use
define_methodor ghost-method caching when the pattern is known and performance matters.
class Whisper
def method_missing(name, *args, &block)
puts "Someone called: #{name}"
end
end
w = Whisper.new
w.hello # => Someone called: hello
w.goodbye # => Someone called: goodbye
The example above catches every method name indiscriminately, which works for a quick demonstration but would be reckless in a real class. A production method_missing should match only the names it intends to handle and delegate the rest to super, so the caller still gets a meaningful NoMethodError for unexpected calls. This guide will show you both the basic mechanics and the safeguard patterns that keep the hook predictable.
basic syntax
method_missing receives the name of the called method as a Symbol, followed by any arguments and an optional block:
def method_missing(name, *args, &block)
# name — a Symbol, e.g. :find_by_email
# args — an Array of positional arguments
# block — the block if one was passed, nil otherwise
end
The parameters work exactly like a regular method definition. There’s no magic; just convention.
That convention is key to using the hook well. The method name arrives as a symbol, the arguments arrive as an array, and the block arrives as a block parameter. Once you see that shape, you can treat the hook like any other parser: inspect the name, decide whether it matches your pattern, then handle the call or pass it along to super. The next example puts all three parameters to work at once and shows the full method signature in context, matching method names against known patterns before falling back to a sensible default through Ruby’s built-in super.
class Greeter
def method_missing(name, *args, &block)
case name
when :greet then puts "Hello, #{args.first || 'World'}!"
when :shout then puts args.first.upcase if args.any?
else super
end
end
end
Greeter.new.greet # => Hello, World!
Greeter.new.greet("Alice") # => Hello, Alice!
Greeter.new.shout("hi") # => HI
respond_to_missing?; accurate introspection
Here’s a subtle trap: respond_to?(:some_method) checks the method table directly. It knows nothing about what method_missing handles. Call it on the Greeter above and it returns false; even though greet works fine.
Fix this by overriding respond_to_missing?:
class Greeter
KNOWN = [:greet, :shout]
def method_missing(name, *args, &block)
case name
when :greet then puts "Hello, #{args.first || 'World'}!"
when :shout then puts args.first.upcase if args.any?
else super
end
end
def respond_to_missing?(name, include_private = false)
KNOWN.include?(name) || super
end
end
g = Greeter.new
g.respond_to?(:greet) # => true
g.respond_to?(:foo) # => false
The rule is simple: if you override method_missing, also override respond_to_missing?. Return true only for the methods you actually handle; never unconditionally, or introspection lies.
That extra method keeps the class trustworthy for callers that probe capabilities before invoking them. Autocomplete, delegation helpers, serializers, and debugging tools all behave better when respond_to? matches the same rule set as method_missing. The overhead is minimal and the payback is real: your dynamic object looks normal to the rest of the Ruby ecosystem instead of quietly claiming it can handle method names that will fail at runtime when actually called by another part of the system.
preventing NoMethodError from being swallowed
The biggest danger with method_missing is that it can silently absorb method calls that should fail. If your implementation calls super for unknown methods, NoMethodError eventually gets raised. But if you forget super, bugs become invisible.
There are two clean ways to guard against this.
Option 1: Explicit guard with known methods
class StrictRouter
ALLOWED = [:get, :post, :put, :delete, :patch]
def method_missing(name, *args, &block)
if ALLOWED.include?(name)
register_route(name, args.first, block)
else
raise NoMethodError, "undefined method `#{name}' for #{self.class}", caller
end
end
private
def register_route(verb, path, block)
puts "Registered #{verb} #{path}"
end
end
r = StrictRouter.new
r.get "/users" # => Registered get /users
r.foo "/bar" # => NoMethodError: undefined method `foo' for #<StrictRouter>
Option 2: Use undef_method for extra safety
undef_method removes a method from the lookup table entirely, so any call goes through method_missing or raises NoMethodError. This blocks inheritance too: a subclass can’t bring the method back:
class Greeter
def hello
"Hello!"
end
end
class StrictGreeter < Greeter
# Remove inherited hello so it triggers method_missing instead
undef_method :hello if method_defined?(:hello)
def method_missing(name, *args, &block)
"Ghost method: #{name}"
end
end
g = StrictGreeter.new
g.hello # => "Ghost method: hello"
In practice, the explicit guard (Option 1) combined with respond_to_missing? is the most maintainable approach for most codebases.
When you are unsure, default to a narrow matcher instead of a broad catch-all. It is easier to expand a guarded method_missing later than it is to untangle a version that quietly swallowed names it should never have accepted.
Real-world examples
delegation: forwarding calls to another object
The Forwardable module in the standard library uses method_missing under the hood, but you can implement it directly:
class UserController
def initialize
@model = User
end
def method_missing(name, *args, &block)
if @model.respond_to?(name)
@model.send(name, *args, &block)
else
super
end
end
def respond_to_missing?(name, include_private = false)
@model.respond_to?(name) || super
end
end
Delegation is one of the few places where method_missing can feel natural. The object acts like a facade, but the real implementation lives somewhere else. If you use this pattern, keep the forwarding rules obvious so future readers can tell which object actually owns the behavior.
activerecord-style dynamic finders
When you call User.find_by_email("alice@example.com"), Rails parses the method name, extracts the attribute, and runs a query. Here’s the pattern:
class ActiveRecordFinder
def find_by(attribute, value)
puts "SELECT * FROM users WHERE #{attribute} = '#{value}'"
end
def method_missing(name, *args, &block)
if name.to_s.start_with?("find_by_")
attr_name = name.to_s.sub("find_by_", "")
find_by(attr_name, *args)
else
super
end
end
end
finder = ActiveRecordFinder.new
finder.find_by_email("alice@example.com")
# => SELECT * FROM users WHERE email = 'alice@example.com'
Dynamic finders are a good example of when method_missing does real work instead of just adding cleverness. The method name itself carries meaning, so the dynamic call reads like part of the domain. That is much easier to justify than using the hook for arbitrary shortcuts.
DSL building: chainable methods
method_missing shines for building fluent APIs where any method call adds to a chain:
class Query
def initialize
@clauses = []
end
def method_missing(name, *args, &block)
@clauses << "#{name}(#{args.map(&:inspect).join(', ')})"
self # return self to enable chaining
end
def to_sql
@clauses.join("\n")
end
end
query = Query.new
query.select("*").from("users").where(name: "Alice").limit(10)
puts query.to_sql
# => select("*")
# => from("users")
# => where(name = "Alice")
# => limit(10)
Chainable DSLs are another place where the hook can fit, but the API should still read like a sentence. If every call simply appends another clause, the builder becomes easy to scan and the output is predictable. If the chain hides too many side effects, switch back to explicit methods.
method_missing vs define_method
| Aspect | method_missing | define_method |
|---|---|---|
| When to use | Unknown method names at runtime | Known at class definition time |
| Performance | Slower; lookup on every call | Faster; method in the method table |
| Flexibility | Handles any method name pattern | Requires known name upfront |
| Introspection | Needs respond_to_missing? | Works with respond_to? out of the box |
| Debugging | Can mask bugs silently | Easier to trace and debug |
If you know the method names when writing the class, use def or define_method. Save method_missing for when method names are truly dynamic; parsed from a string, derived from data, or part of a DSL.
The performance tradeoff is simple: define_method puts the method in the lookup table up front, while method_missing waits until Ruby asks for a name that does not exist yet. That means define_method is easier to reason about when the method list is stable, and method_missing is better when the names are only known at runtime.
Performance and ghost method caching
Every method_missing call pays a lookup penalty: Ruby searches the method table, fails to find it, then calls your hook. For frequently called methods, this adds up.
The solution is the ghost method caching pattern: after the first call via method_missing, define the method for real using define_singleton_method, so subsequent calls skip the hook entirely:
class MathConstants
LOOKUP = { pi: 3.14159, e: 2.71828, phi: 1.61803 }
def method_missing(name, *args, &block)
if LOOKUP.key?(name)
# Define the method on this object only
define_singleton_method(name) { LOOKUP[name] }
LOOKUP[name]
else
raise NoMethodError, "undefined method `#{name}'", caller
end
end
end
c = MathConstants.new
puts c.pi # => 3.14159 (first call — via method_missing)
puts c.pi # => 3.14159 (second call — singleton method)
puts c.e # => 2.71828
The first call to c.pi triggers method_missing and defines a singleton method. The second call finds pi directly in the method table; no hook, no lookup overhead.
Ghost method caching is useful when a dynamic method is likely to be called many times after the first lookup. It gives you the flexibility of method_missing with most of the speed of a normal method, which is a good compromise for hot paths that still need dynamic setup.
const_missing: the same hook for constants
Constants have an analogous hook: const_missing. Ruby calls it when it encounters an undefined constant reference:
class Object
def self.const_missing(name)
puts "Attempted to access undefined constant: #{name}"
end
end
FooBar # => Attempted to access undefined constant: FooBar
Rails uses this extensively for autoloading. When you reference User and it hasn’t been loaded yet, const_missing fires, Rails finds the file (app/models/user.rb), loads it, and retry the constant lookup; all invisible to you.
That is the important pattern to notice: Ruby gives classes and modules the same kind of fallback hooks for more than one kind of lookup. Once you understand method_missing, const_missing feels familiar, because both hooks let you intercept an unresolved name and decide what should happen next.
Forward link
If you want to keep going, the next step is to compare this hook with other Ruby metaprogramming tools. define_method creates methods whose names are known at class-load time, send invokes methods dynamically without bypassing the method table, and Rails callback hooks like before_save fire at specific lifecycle points. Each tool solves a different part of the metaprogramming puzzle, and knowing when to reach for method_missing over the alternatives is what separates readable dynamic code from a debugging headache.
See Also
- Ruby Metaprogramming Basics; Core concepts of Ruby’s open class model,
send, andconst_missing - Ruby Classes and Objects; Object model fundamentals: method lookup,
self, and inheritance hierarchy - Ruby define_method Tutorial; Dynamic method creation that complements
method_missing