Ruby Metaprogramming - Method Missing
What is method_missing?
method_missing
is a method that ruby gives you access inside of your objects a way to handle situations when you call a method that doesn't exist. It's sort of like a Begin/Rescue, but for method calls. It gives you one last chance to deal with that method call before an exception is raised.
What does it look like?
method_missing
accepts 3 parameters.
- The first is the name of the method you were trying to call.
- The second is the args (
*args
) that were passed to the method. - The third is a block (&block) that was passed to the method.
The 2nd and 3rd params may be empty if the method was called without arguments, but they are there anyway for you to use and/or pass on to another method.
def method_missing(m, *args, &block)end
Using method_missing
I can immediately think of 2 uses for using method_missing in your code.
- Dealing with repetitive methods that function the same: I recently wrote an article on creating a DSL in Ruby. For the example I created a DSL for generating HTML tags. I didn't want to define a method for every single HTML tag, so instead I just used method_missing, to sort of dynamically create HTML tags as they were called.
- Delegating method calls to another object: You could write a simple presenter and define your own delegation method using
method_missing
. This is the example I am going to present below. Most likely you would want to use SimpleDelegator to implement this, but for the sake of demoing how it works we will do it ourselves.
Creating a Presenter
Using Delegation and Method Missing
We are going to create a Presenter class. The purpose of the class is to accept an object in the constructor, and any methods we call that aren't specifically implemented by our Presenter will be delegated to the object it was instantiated with.
class Presenterattr_accessor :objectdef initialize(object)self.object = objectend# If a method we call is missing, pass the call onto# the object we delegate to.def method_missing(m, *args, &block)puts "Delegating #{m}"object.send(m, *args, &block)endend
We are going to extend from our Presenter class to create a UserPresenter, specifically used for presenting User objects.
class UserPresenter < Presenter# We just want to display the first letter of the last namedef last_name"#{object.last_name[0]}."enddef full_name"#{first_name} #{last_name}"endend# A mini User object to work withUser = Struct.new(:first_name, :last_name, :age)user = User.new("Leigh", "Halliday", 30)user_presenter = UserPresenter.new(user)puts user_presenter.full_nameputs user_presenter.age
Here is the output for our calls above. Notice that it delegates the first_name
method to the User object, as well as the age
method, but last_name
isn't delegated because we wrote our own method to handle it.
Delegating first_nameLeigh H.Delegating age30
Re-Creating the Alias method
To illustrate method_missing
being used another way, we'll re-implement the alias
method, calling it mimic
. What we'll do is create a mimic
method which is called at the class level. Its job is to build a lookup table of the different aliases our methods have.
Once we call a method that doesn't exist, our code will attempt to find an alias for that method before having to fail.
class Mimic@@mimic_lookup = {}def self.mimic(to, from)@@mimic_lookup[to] = fromenddef method_missing(m, *args, &block)if @@mimic_lookup.include?(m.to_sym)self.send(@@mimic_lookup[m.to_sym], *args, &block)elseraise ArgumentError.new("Method `#{m}` doesn't exist.")endenddef respond_to?(method_name, include_private = false)@@mimic_lookup.include?(method_name.to_sym) || superendendclass Alpaca < Mimicmimic :saludar, :greetdef greetputs "Hey there"endendbuddy = Alpaca.newp buddy.respond_to?(:saludar)buddy.saludarbuddy.welcome
We see that it was able to successfully greet using the alias saludar
(Spanish for greet), and then it correctly raised an exception when we called the welcome
method.
trueHey therealias.rb:13:in `method_missing': Method `welcome` doesn't exist. (ArgumentError)from alias.rb:31:in `<main>'
As pointed out in an article by thoughtbot about method_missing
, it's always good to override the respond_to? method when you are working with method_missing
.
I recommend Metaprogramming Ruby 2 to go deeper on this subject.