method_missing done right

The pair

method_missing is called when the lookup fails. Overriding it handles a name the class does not define, and respond_to_missing? answers the matching question for respond_to?. They are a pair: one without the other produces an object that lies about what it can do.

def method_missing(name, *args) return super unless name.to_s.end_with?("=") || args.empty? @values ||= {} if name.to_s.end_with?("=") @values[name.to_s.chomp("=")] = args.first else @values[name.to_s] end end def respond_to_missing?(name, include_private = false) name.to_s.end_with?("=") || (@values || {}).key?(name.to_s) || super end

super in both is the part that keeps Ruby's error messages. A method_missing that never calls super turns every typo into silence.

A settings object

A settings hash with attribute-style access is the small case that earns the pair. Reading and writing work, and an unknown call with an argument still raises:

config = Config.new config.color = "red" p config.color p config.respond_to?(:color) p config.respond_to?(:nope) begin config.nope(1) rescue NoMethodError => e puts "NoMethodError: #{e.message}" end

ruby missing.rb

"red" true false NoMethodError: undefined method 'nope' for an instance of Config

The call with an argument goes to super because the guard rejects it, so Ruby builds the usual message. The respond_to? answers are the ones a framework or a serialiser checks before it calls.

respond_to_missing?

Ruby calls respond_to_missing? only after the normal lookup fails, and only from respond_to?. It must not raise, and it must accept the second argument:

def respond_to_missing?(name, include_private = false) name.to_s.end_with?("=") || (@values || {}).key?(name.to_s) || super end

The method is a promise: when it answers true, a caller may call the name and expect method_missing to handle it. A promise that is broader than the handler produces a NoMethodError from code that checked first.

When it is wrong

What to watch for

method_missing on Object changes every object in the process. A library that does it is a global change, and two such libraries can disagree about a name.

A handler that is slow is called for every missing name, including the ones from inspect and respond_to? probes. Keep it to a hash lookup and let super do the rest.

Next: define_method and small DSLs

Article author: Arthur Isaev