Skip to content
AgileLogics
5 min read

method_missing and respond_to_missing?

Prerequisites: Metaprogramming: send and define_method

When you call a method that doesn't exist, Ruby doesn't give up immediately: it calls method_missing on the object, passing the name and arguments. The default implementation raises NoMethodError — but you can override it.

Example: a wrapper that forwards dynamic readers

class Settings
  def initialize(values)
    @values = values
  end

  def method_missing(name, *args)
    key = name.to_s
    if key.end_with?("?") && @values.key?(key.chomp("?").to_sym)
      !!@values[key.chomp("?").to_sym]
    elsif @values.key?(name)
      @values[name]
    else
      super
    end
  end

  def respond_to_missing?(name, include_private = false)
    @values.key?(name.to_s.chomp("?").to_sym) || super
  end
end

settings = Settings.new({ theme: "dark", beta: true })
p settings.theme
p settings.beta?
p settings.respond_to?(:theme)
settings.colour
"dark"
true
true
settings.rb:13:in 'Settings#method_missing': undefined method 'colour' for an instance of Settings (NoMethodError)
	from settings.rb:26:in '<main>'

The error is raised by the super call on line 13 — the default method_missing — which is why it's reported from inside Settings#method_missing.

Two rules that matter:

  1. Call super for names you don't handle, so unknown methods still raise a normal NoMethodError instead of silently returning nil.
  2. Always define respond_to_missing? alongside it, so respond_to? and method(:theme) tell the truth about what the object supports.

Prefer the alternatives

method_missing is slower than a real method (Ruby has to fail a lookup first), invisible to editors and documentation, and easy to get subtly wrong. Before reaching for it, consider:

  • define_method — if the set of names is known when the class loads (as it is for @values keys in many cases), generate real methods up front.
  • Explicit delegation — Forwardable or SimpleDelegator (covered in the Design Patterns lesson) forward calls to another object without any magic.

Reach for method_missing when the set of names is genuinely open- ended — proxies, wrappers around remote APIs, or builder DSLs.