rubyguides

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_missing when the method names are genuinely dynamic.
  • Pair it with respond_to_missing? so respond_to? stays honest.
  • Prefer super or an explicit error for names you do not handle.
  • Use define_method or 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

Aspectmethod_missingdefine_method
When to useUnknown method names at runtimeKnown at class definition time
PerformanceSlower; lookup on every callFaster; method in the method table
FlexibilityHandles any method name patternRequires known name upfront
IntrospectionNeeds respond_to_missing?Works with respond_to? out of the box
DebuggingCan mask bugs silentlyEasier 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.

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