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’ve probably used without realizing it.
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
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.
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.
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.
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
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'
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)
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.
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.
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.
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