Method Missing and Dynamic Methods

Published by Programming Line in Ruby. Last updated .

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

  1. A caller sends a message, e.g. obj.foo(1, 2).
  2. Ruby searches obj‘s singleton class for a method named foo.
  3. If not found, it searches obj‘s class, then any modules included in that class, in reverse order of inclusion.
  4. It continues up the superclass chain (and each superclass’s included modules) until it reaches Object, Kernel, and BasicObject.
  5. If no matching method turns up anywhere in that chain, Ruby calls obj.method_missing(:foo, 1, 2) instead of raising immediately.
  6. Your override decides: handle the call and return a value, or call super to fall through to BasicObject#method_missing, which raises NoMethodError.
  7. If you also override respond_to_missing?, calls to respond_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_missing with a matching respond_to_missing? override so respond_to? and method stay accurate.
  • Always call super for names you don’t explicitly handle, so real typos and bugs still raise NoMethodError instead of failing silently.
  • Keep the condition inside method_missing narrow (a clear prefix, a known key set) so you don’t accidentally intercept unrelated typos.
  • Capture &block in your method_missing signature whenever a caller might legitimately pass one, and forward it if you delegate the call.
  • Prefer define_method over method_missing whenever the set of dynamic method names is knowable in advance — it’s faster on repeated calls and fully visible to introspection.
  • Use method_missing sparingly 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_missing is called automatically whenever normal method lookup along the ancestor chain fails to find a matching method.
  • Its default implementation, inherited from BasicObject, raises NoMethodError — always call super for names your override doesn’t handle so that behavior is preserved.
  • respond_to_missing? is the required companion to method_missing; without it, respond_to? and method report dynamically handled names as unsupported.
  • define_method creates real, permanent methods at runtime and is the better choice whenever the set of method names is finite and knowable in advance.
  • Because method_missing hides an object’s real interface from introspection tools, use it narrowly, document it, and prefer define_method when you can.