The other day, I was thinking about writing this story when one of my colleagues heard me talking about Paolo Perrotta. He sent me one of his conferences from 2012 (reference). On it, Perrotta talked about the advantages of using
method_missing to avoid code duplication on a Ruby app. However, with every great power, comes great responsibility (deep hero voice 🕸)
In a previous story, we talked about dynamic dispatching and the fact that Ruby, as a language, is interpreted and not compiled, allowing us to do things out of normal. For example, defining a method on runtime when needed. However, that is not the only way of getting rid of duplicated code.
In this story, following Perrotta’s talk, we are going to talk about Ghost methods and the use of
method_missing . Let’s take the initial code we had on a previous example
First, in order to avoid code duplication, we can try and extract the person to be congratulated to another class, let’s say BirthdayPerson. Additionally, let’s try to up the notch a bit and suppose we have a file containing all the people we can congratulate for their birthday, BirthdayDatabase.
Let’s define BirthdayPerson:
Here we have a couple of interesting points to note. First, take a look at the initialisation method.
allowed_people returns all the different people we have in our “database” that we could want to congratulate, each one of them being returned as a symbol.
Then by calling
create_birthday we will define each one of the available methods to congratulate the people in our “database”.
As the reader can observe, the way of defining the methods is quite different from the method we used in a previous story. The reason for that change lays in the fact that we are defining methods at the instance level, rather than the singleton level, so that all instances of the class share the same methods.
A singleton method is a method only available for a “single” instance of the object
define_method (reference) will define the methods resulting from the people in our database.
We have now an interface to map the people in our database to methods that can be called or “sent” 😉
It is time then to refactor our base code and remove code duplication.
But first, a small note on Ruby runtime.
In Ruby, there is no compiler that enforces method calls due to its dynamic nature. This means that you could call a method that doesn’t exist
But, if that is the case, what is happening when a non-existent method is called? What is returning
NoMethodError: undefined method 'method'? Where do all those send methods go, spam folder? (Sorry for the bad joke 😆) .Too many questions, very few answers.
Well, what starts in Ruby ends in Ruby. Let’s bring
method_missing and ancestors into the stage.
When Ruby does not find a method in a
Class or any of its ancestors, it calls
method_missing . However, it is just another method that, as we saw in our ancestors story, is called on each element of the
ancestors' chain until responded. It is like that time you want to go for a run with your friends, you send the message hoping for it to be answered.
In this case,
method_missing will always be answered, if not by the closer ancestors, by
Object and then
BasicObject , returning that sweet
NoMethodError message. However, knowing what we know about Ruby, where method names are sent on the
ancestors' chain and the method named is sent from bottom to top, how could we use this to our advantage and duplicate code?
Knowing what we know on
method_missing and our knowledge on Ruby ancestors we could try to play a little with it.
Let’s imagine the first piece of code I showed you in this article:
And refactor it to use our abstraction to
Looks way better and simpler, right? We are just delegating the method to
BirthdayPerson by sending it the method name.
However, we still have to define the
congratulate method. So, would it be possible to refactor the code in any way that no new method is defined? 🤔
What about using
method_missing and Ruby’s ancestors? That’s it!
Boom!! If you take a look, you’d see that
congratulate has been replaced by
method_missing, which will be the one sending the method name to our
BirthdayPerson object. Thus, saving us from defining a new method by taking advantage of Ruby’s
But, how is this happening under the hood?
A drawing may help, right?
The drawing above tries to explain the path the
Interpreter of Ruby took to find the method sent. First, it tried to find it on the current object it was evaluating, an instance of
BirthdayCongratulations. As it did not find it there, it went up on the
ancestors' chain performing
method_lookup until the method was found.
When it got to the top of the chain without any match, the
method_missing on the first element on the chain, where the original call to the method was sent. Finding a match for
method_missing at that level, it proceeds to execute the code inside.
In this case, the code inside was a call on an
BirthdayPerson that will execute the desired code.
Such scenario and availability for a method that is not within the object it gets called on, nor any of its ancestors, is called a
Ghost method. It is there for the interpreter, but it is not really there. If we called
birthday_congratulations the output would be:
So 🤔 The method call works on the object, but the object does not
respond_to the method? Looks like something that makes sound but is not there. A ghost!!
The concept of
Ghost method is really interesting and can be useful when developing things like gems or frameworks, but it still can cause some problems for implementations that expect objects to
respond_to the method called upon. This is not a huge deal normally but is better to be aware of. Also, of course, there is a solution 😉
As the reader may have noticed by now, in Ruby nothing is what it seems.
So, why would be
respond_to?? If we inspect the method (reference), we would see that it calls
respond_to_missing? if it can’t find the method on the
ancestors' chain of the class. Notice a behaviour similar to the one happening when the
Interpreter can’t find the method on the object.
Bearing this in mind, implementing
respond_to_missing? is key when applying the
Ghost method pattern. If not implemented, again, it is not a big deal, but can cause problems if the object is used in another programs or in ways we did not anticipate. Better safe than sorry 😉
As of right now, you are almost a gurú on
Ghost methods but there are still some nuances worth mentioning. But, let’s leave it for another story as I think this one is dense enough.
I hope you enjoyed todays’ story. Don’t be scared of ghosts 👻 😉
See you around!!