ruby_cool_kid.rb — Meta Programming series: Ghost Methods, pulling the sheets
As of right now, you are almost a gurú on Ghost methods
, if you followed our previous story , but there are still some nuances worth mentioning.
Let’s first talk about the Ghost within the Ghost
Ghost within the ghost
Implementing method_missing
can be a good practice that saves time for a developer, but can also carry some unexpected errors with it.
Taking the code explained above, what would happen if we modified method_missing
to covert the name of the person to lower case?
What?
Why does it throw an exception? Look closely to where the name is converted to lower case. Do you see it?
Of course, everything was going good but we had to make a typo 😓. But, if it was a typo, why doesn’t it just throw a NoMethodError
exception?
Well, if you remember, we overwrote the method_missing
method to match our desires, and we call the method that has the typo within it. So, Ruby falls into a loophole of not finding the method and going into method_missing
, which has our definition for the method with the typo, going again into method_missing
, until it throws the SystemStackError
due to the stacked calls to method_missing
. Interesting, right? 😅
Fake Ghosts
It can also happen, that a method we thought was going to be the perfect ghost, gets played by Ruby’s own methods for the objects in the ancestors' chain
.
Let’s suppose that we define a method on BirthdayCongratulations
called display
that will return all the possible congratulations messages based on the records in our database. Of course, we would need to refactor our classes so that the default is not an interpolation with congratulate_
but those are just nuances for this scenario.
What would happen if display
is called following the Ghost method
pattern. It won’t be called on BirthdayPerson
as you’d expect looking at our implementation of method_missing
. It would be called on Object
(reference).
If you remember what we said about method_missing
is that, before triggering it, the Interpreter
will look up on the ancestor's
chain for a match and, if not found, trigger method_missing
.
However, in this case, display
is found and will stop the Interpreter
from calling method_missing
.
Blank slate
One way of solving this would be to undefine every instance method for the class so that the calls will not collide:
Buuuut, this may remove some essentials methods for Ruby. So, what can be the alternative?
Well, Ruby serves us with a class that has just the basic methods needed for an object to work in Ruby, BasicObject
. Inheriting from BasicObject
ensures that we just have the minimum methods for an object to work, thus reducing the probabilities of collision.
This pattern is also known as a blank slate
.
That’s all for today folks, I hope you enjoyed the story and do not be scared of Ghosts 😉
See you around!!
Pablo