Ghost Methods in Ruby (translation)

I bring to your attention a translation of a fragment of the book Metaprogramming Ruby 2 by Paolo Perrotta.

What is method_missing?


In Ruby, we can call methods that do not exist, but this will return an error to us. For example:

class Lawyer; end
nick = Lawyer.new
nick.talk_simple
NoMethodError: undefined method 'talk_simple' for #

Do you remember how method searches work? When you call the talk_simple method, Ruby goes to the nick object class and iterates over the methods there. If he cannot find the method there, he searches for it in the parents of the given class, then in Object and finally in BasicObject. Well, since Ruby cannot find the talk_simple method anywhere, it runs the method_missing method for nick. Ruby knows that this method exists, because it is a private BasicObject method from which all objects are inherited.

You can call this method for experiment. Since the method is private, you can do it like this:

nick.send :method_missing, :my_method
NoMethodError: undefine method 'my_method' for #

You just did what Ruby does, you told the object, “I just tried calling the method: my_method, but it is not there, so give me an error.” BasicObject # method_missing returns NoMethodError, in fact for this it exists.

Method_missing override


Most likely you will never need to call the method_missing method yourself. Instead, you can rewrite this method to intercept unknown calls. Each such call stores information about the name of the method and the arguments that were passed.

class Lawyer
  def method_missing(method, *args)
    puts "You called: #{method}(#{args.join(', ')})"
    puts "(You also passed it a block)" if block_given?
  end
end
bob = Lawyer.new
bob.talk_simple('a', 'b') do
# a block
end
You called: talk_simple(a, b)
(You also passed it a block)

Overriding the method method_missing gives you the ability to call methods that are not actually described, that is, do not exist. Consider a little more detailed.

Ghost methods


When you need to declare many similar methods, calling them through method_missing will help you to avoid this. This is how to tell the object that “If a method is called that does not exist, just do it.” From the side of the method call that works through method_missing, everything looks like a normal call to a regular method, but inside the class, this method simply does not exist. This trick is called the Ghost Method. Let's look at some examples.

Hashie example


The Hashie Gem contains some magic called Hashie :: Mash. Mash is a more powerful version of the Ruby OpenStruct standard library; it is a hash-like object whose attributes work like Ruby variables. If you need a new attribute, you simply declare the values ​​for that attribute. Here's how it works.

require 'hashie'
icecream = Hashie::Mash.new
icecream.flavor = 'strawberry'
icecream.flavor                        # => 'strawberry'

This works because Hashie :: Mash is a subclass of Ruby Hash, and these attributes are essentially ghost methods. Here's how method_missing is implemented here:

module Hashie
  class Mash< Hashie::Hash
    def method_missing(method_name, *args, &blk)
      return self.[](method_name, &blk) if key?(method_name)
      match = method_name.to_s.match(/(.*?)([?=!]?)$/)
      case match[2]
      when "="
        self[match[1]] = args.first        
        # ...
      else
        default(method_name, *args, &blk)
      end
    end
    # ...
  end
end

If the name of the called method is the key name in the hash (like flavor), then Hashie :: Mash # method_missing simply calls the [] method to return the corresponding value. If the name ends with “=”, then method_missing cuts off the extra characters and gets the value that needs to be saved. If the name does not contain something else, then method_missing simply returns some standard value. Hashie :: Mash also contains other special characters, such as “?”.

respond_to_missing?


If you want to test a ghostly method with respond_to?, Then Ruby's obvious business will lie to you.

john = Lawyer.new
john.respond_to?(:talk_simple)        # => false

This can be problematic as we can often use respond_to for checks. But Ruby provides a good mechanism to make friends respond_to and ghost methods.

respond_to? calls the respond_to_missing method? which returns true if it is a ghostly method. To ensure that this does not bring more problems during development, redefine each time along with method_missing and respond_to_missing.

class Lawyer
  # ...
  def respond_to_missing?(method, include_private = false)
    methods.include?(method) || super
  end
end
bill = Lawyer.new
bill.respond_to?(:talk_simple)          # => true

A little earlier rubists redefined respond_to itself? method. But now there is respond_to_missing? and override respond_to? It is considered as not the correct option for working with method_missing.

Ghost Methods are just a few of the interesting features in Ruby.

Also popular now: