Method Missing and Dynamic Methods
Every method call in Ruby begins with a search: Ruby walks up the object’s ancestor chain looking for a method with that name. When the search comes up empty, Ruby doesn’t just give up — it gives the object one more chance by calling a special method named method_missing. This single hook is what lets Ruby objects answer to method names that were never explicitly defined, and it’s the engine behind dynamic attribute readers, proxy objects, and the elegant DSLs Ruby is known for. Paired with define_method, which lets you generate real methods programmatically, these tools give you precise control over how an object responds to the messages sent to it.
Overview / How It Works
In Ruby, calling obj.foo(1, 2) is really sending the message :foo with arguments 1 and 2 to obj. To resolve that message, Ruby searches obj‘s singleton class, then its class, then any modules included in that class (searched in reverse order of inclusion), then the superclass and its included modules, and so on up to Object, Kernel, and finally BasicObject. This chain is called the method lookup path (or ancestor chain), and you can inspect it yourself with SomeClass.ancestors.
If that entire search comes up empty, Ruby does not immediately raise an error. Instead, it calls one more method on the receiver: method_missing, passing the original method name as a Symbol, the arguments that were passed, and any block. BasicObject defines a default method_missing that simply raises NoMethodError — which is the error you normally see for typos. Because every object inherits this hook, you can override it in your own classes to intercept calls that have no matching method and decide, at runtime, how to respond.
This is exactly how libraries like OpenStruct implement attributes that don’t exist until you assign them, how ORMs historically supported dynamic finder methods, and how proxy or delegator objects forward arbitrary calls to a wrapped object. The tradeoff is that method_missing makes an object’s public interface invisible to tools that inspect real methods (documentation generators, respond_to?, autocomplete), which is why it’s almost always paired with respond_to_missing? — a companion hook that lets you tell Ruby’s introspection methods (respond_to?, method) that your object handles a name even though no real method exists for it.
The other tool for dynamic behavior is define_method, a private method available inside any class or module body. Unlike method_missing, which intercepts a failed lookup every single time, define_method creates a genuine, permanent method the moment it runs. Once defined, calls to it are found normally during ancestor-chain lookup — no interception, no fallback path, and respond_to? reports it correctly with zero extra work. When the set of method names you need is knowable (even if it comes from a list, a database schema, or a hash of options), define_method is almost always the better, faster, more introspectable choice. Reach for method_missing only when the set of valid names is genuinely open-ended and can’t be enumerated in advance.
Syntax
class MyClass
def method_missing(method_name, *args, &block)
if method_name.to_s.start_with?("prefix_")
# handle the call dynamically and return a value
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?("prefix_") || super
end
end
| Part | Meaning |
|---|---|
method_name |
A Symbol for the method that was called but not found |
*args |
Whatever positional arguments the caller passed |
&block |
The block passed to the call, if any (capture it if you need to forward it) |
super (in method_missing) |
Falls through to BasicObject#method_missing, raising NoMethodError for names you don’t handle |
respond_to_missing? |
Return true/false to keep respond_to? accurate for dynamically handled names |
define_method(name) { |*args| ... } |
Defines a real, permanent instance method named name at the point it’s called |
Examples
Example 1: Intercepting calls with method_missing
class Ghost
def method_missing(name, *args, &block)
if name.to_s.start_with?("say_")
word = name.to_s.sub("say_", "")
"#{word}!" * args.first.to_i
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?("say_") || super
end
end
ghost = Ghost.new
puts ghost.say_boo(3)
puts ghost.respond_to?(:say_boo)
puts ghost.respond_to?(:fly)
begin
ghost.fly
rescue NoMethodError => e
puts "Error: #{e.class}"
end
Output:
boo!boo!boo!
true
false
Error: NoMethodError
ghost.say_boo(3) doesn’t match any real method, so Ruby calls method_missing(:say_boo, 3). Because the name starts with say_, the code strips the prefix, builds the string "boo!", and repeats it three times with String#*. respond_to?(:say_boo) returns true only because respond_to_missing? was overridden to recognize the same prefix. Calling ghost.fly falls through to super inside method_missing, which reaches BasicObject‘s default behavior and raises a real NoMethodError, exactly as it would for an object with no method_missing at all.
Example 2: A dynamic record with attribute-style access
class DynamicRecord
def initialize(attributes = {})
@attributes = attributes
end
def method_missing(name, *args)
key = name.to_s
if key.end_with?("=")
@attributes[key.chomp("=").to_sym] = args.first
elsif @attributes.key?(name)
@attributes[name]
else
super
end
end
def respond_to_missing?(name, include_private = false)
key = name.to_s
@attributes.key?(name) || key.end_with?("=") || super
end
end
record = DynamicRecord.new(title: "Ruby Basics", views: 120)
puts record.title
puts record.views
record.rating = 4.8
puts record.rating
puts record.respond_to?(:title)
Output:
Ruby Basics
120
4.8
true
This class wraps a plain Hash and uses method_missing to turn arbitrary method calls into hash lookups and arbitrary assignment calls (names ending in =) into hash writes. record.title and record.views read straight from @attributes. record.rating = 4.8 is dispatched as method_missing(:rating=, 4.8), which strips the trailing = and stores the value under :rating. Note that the expression record.rating = 4.8 itself always evaluates to 4.8 in Ruby, regardless of what the method returns — that’s a language rule for any assignment-style call, not something this class controls.
Example 3: Generating real methods with define_method
class Temperature
CONVERSIONS = {
to_fahrenheit: ->(c) { c * 9.0 / 5 + 32 },
to_kelvin: ->(c) { c + 273.15 }
}
CONVERSIONS.each do |name, formula|
define_method(name) do
formula.call(@celsius)
end
end
def initialize(celsius)
@celsius = celsius
end
end
temp = Temperature.new(25)
puts temp.to_fahrenheit
puts temp.to_kelvin
Output:
77.0
298.15
Instead of intercepting failed calls, this class loops over a hash of conversion lambdas at class-definition time and calls define_method once per entry, creating two ordinary instance methods: to_fahrenheit and to_kelvin. There is no runtime interception at all — by the time Temperature.new(25) is created, to_fahrenheit and to_kelvin are real methods sitting in Temperature‘s method table, found by normal lookup, and fully visible to respond_to? and instance_methods without any extra code.
How It Works Step by Step
- A caller sends a message, e.g.
obj.foo(1, 2). - Ruby searches
obj‘s singleton class for a method namedfoo. - If not found, it searches
obj‘s class, then any modules included in that class, in reverse order of inclusion. - It continues up the superclass chain (and each superclass’s included modules) until it reaches
Object,Kernel, andBasicObject. - If no matching method turns up anywhere in that chain, Ruby calls
obj.method_missing(:foo, 1, 2)instead of raising immediately. - Your override decides: handle the call and return a value, or call
superto fall through toBasicObject#method_missing, which raisesNoMethodError. - If you also override
respond_to_missing?, calls torespond_to?,method, and similar introspection reflect your dynamic behavior accurately instead of reporting the object as not understanding a message it actually handles.
Common Mistakes
Mistake 1: Overriding method_missing without respond_to_missing?
class Proxy
def method_missing(name, *args)
"handled #{name}"
end
end
proxy = Proxy.new
puts proxy.respond_to?(:anything)
Output:
false
Even though proxy.anything would work fine, respond_to?(:anything) reports false, because respond_to? only consults real methods and respond_to_missing? — it never calls method_missing itself. Any code that checks respond_to? before calling (a very common Ruby idiom) will incorrectly skip this object. The fix is to override respond_to_missing? to match the same logic used in method_missing:
class ProxyFixed
def method_missing(name, *args)
"handled #{name}"
end
def respond_to_missing?(name, include_private = false)
true
end
end
proxy = ProxyFixed.new
puts proxy.respond_to?(:anything)
Output:
true
Mistake 2: Forgetting to call super for names you don’t handle
class Loose
def method_missing(name, *args)
nil
end
end
obj = Loose.new
puts obj.tpyo_metod.inspect
Output:
nil
This class swallows every unrecognized call and quietly returns nil — including a plain typo like tpyo_metod. Instead of a loud, easy-to-spot NoMethodError, the bug becomes a silent nil that surfaces somewhere else entirely, often far from where the typo was made. Always call super for any name your method_missing doesn’t explicitly recognize:
class Strict
def method_missing(name, *args)
super
end
end
obj = Strict.new
begin
obj.tpyo_metod
rescue NoMethodError => e
puts "Error: #{e.class}"
end
Output:
Error: NoMethodError
Best Practices
- Always pair
method_missingwith a matchingrespond_to_missing?override sorespond_to?andmethodstay accurate. - Always call
superfor names you don’t explicitly handle, so real typos and bugs still raiseNoMethodErrorinstead of failing silently. - Keep the condition inside
method_missingnarrow (a clear prefix, a known key set) so you don’t accidentally intercept unrelated typos. - Capture
&blockin yourmethod_missingsignature whenever a caller might legitimately pass one, and forward it if you delegate the call. - Prefer
define_methodovermethod_missingwhenever the set of dynamic method names is knowable in advance — it’s faster on repeated calls and fully visible to introspection. - Use
method_missingsparingly and document it clearly; it makes an object’s interface harder to discover by reading the class alone.
Practice Exercises
1. Write a class UnitConverter that stores a value in meters and responds to any method matching to_<unit> (for example to_feet, to_yards) by looking up a conversion factor in a hash and multiplying. Add respond_to_missing? so respond_to?(:to_feet) returns true.
2. Using define_method, write a class User that takes a role keyword argument and dynamically defines predicate methods admin?, editor?, and guest? from a fixed list of roles, each returning true only when it matches the user’s actual role.
3. Given a class that already overrides method_missing to forward unknown calls to an internal @data hash, add a respond_to_missing? override and a call to super for unhandled names, then verify with respond_to? and a rescued NoMethodError that both behave correctly.
Summary
method_missingis called automatically whenever normal method lookup along the ancestor chain fails to find a matching method.- Its default implementation, inherited from
BasicObject, raisesNoMethodError— always callsuperfor names your override doesn’t handle so that behavior is preserved. respond_to_missing?is the required companion tomethod_missing; without it,respond_to?andmethodreport dynamically handled names as unsupported.define_methodcreates real, permanent methods at runtime and is the better choice whenever the set of method names is finite and knowable in advance.- Because
method_missinghides an object’s real interface from introspection tools, use it narrowly, document it, and preferdefine_methodwhen you can.
