Mastering method_missing in Ruby

· 6 min read · Updated March 26, 2026 · intermediate
ruby metaprogramming method-missing

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

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.

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