rubymetaprogramming

Where does "method_missing" come from and how is it called?


When I check if some of the standard-types in Ruby got a "method_missing"-method, then I get false as a response.

irb(main):020> "".respond_to? "method_missing"
=> false

"method_missing" isn't defined on some super-object and then inherited? Where does it come from? But moreover: How does the whole thing work?

How does it know, it has to lookup, if a method_missing-method exists on the object and then calling that method, instead of throwing an exception?


Solution

  • Generally, there are things in Ruby which are implemented using means outside of the language and which you can't express that way from within inside Ruby. This includes many low-level mechanisms such as creating new objects or actually calling a method. Thus, these things can only be approximated as Ruby code with more-or-less pseudocode often.

    With that being said, method_missing is not strictly related to the implementation of respond_to? resp. respond_to_missing?. These two can be implemented entirely independent from each other.

    In general, the (optional) method_missing method is called by the internal Ruby method dispatcher when an object does not accept a message it has been sent (i.e. if the object does not implement the method that has been called on it) If the object implements a method_missing method, it will be called the result of that method will be returned to the caller of the original method. We can approximate this with a custom my_send method:

    class Sender
      def initialize(object)
        @object = object
      end
    
      def my_send(method, *args, **kwargs, &block)
        @object.send(method, *args, **kwargs, &block)
      rescue NoMethodError => original_no_method_error
        begin
          @object.my_method_missing(method, *args, **kwargs, &block)
        rescue NoMethodError
          raise original_no_method_error
        end
      end
    end
    
    class Example
      def foo
        :foo
      end
    
      def my_method_missing(method_name, *args, **kwargs, &block)
        p [:my_method_missing, method_name, args, kwargs]
        super
      end
    
      def respond_to_missing?(method_name)
        p [:respond_to_missing?, method_name]
        super
      end
    end
    
    example = Example.new
    
    sender = Sender.new(example)
    sender.my_send(:foo)
    # => :foo
    
    sender.my_send(:bar, 123, x: 42)
    # [method_missing, :bar, [123], {x: 42}]
    # => undefined method 'bar' for an instance of Example (NoMethodError)
    

    As you can see there, we first try to call the desired method bar on the example object. If that fails, we fall back to the my_method_missing method of the example object which can return a result or raise again.

    The exact mechanism which Ruby itself uses (i.e. the calling of method_missing as part of message sending can not be replicated in Ruby itself). However, similar to the example above, Ruby will call the method_missing method of the receiver if the receiver does not implement the requested method. Ruby can do this as Ruby knows which methods are implemented on an object internally.

    From within Ruby, you can ask an object if it wants to respond to a message (i.e. a method call) by calling respond_to? on the object. The default implementation of respond_to? inspects the same internal repository of defined methods which also used by the message dispatch used when calling a method. If a requested method is not defined, Ruby will then call respond_to_missing? which allows the object to declare additional methods to be handled. If the respond_to_missing? is not defined, Ruby just returns false. If desired, you can overwrite the respond_to? method (although I would not advise you to).

    Often, when defining a custom method_missing method, you would also override respond_to_missing? so that both methods return matching results. You do not have to, however. The important part here is: Ruby will not generally ask an object if it responds to a method. Instead, it will call the method on the object (technically: it sends a message to the object indicating the method to be called) and the object can decide to handle this message. This mechanism is derived from Smalltalk which works the same way.