Where you untangle a twisted yarn of modules, classes, and objects.
You can finally go back to the problem that prompted Bill to launch into his discussion on method lookup, self, and Refinements. You’ve had trouble making sense of a complicated arrangement of classes and modules. Here’s the confusing part:
| module Printable |
| def print |
| # ... |
| end |
| |
| def prepare_cover |
| # ... |
| end |
| end |
| |
| |
| module Document |
| def print_to_screen |
| prepare_cover |
| format_for_screen |
| print |
| end |
| |
| def format_for_screen |
| # ... |
| end |
| |
| def print |
| # ... |
| end |
| end |
| |
| class Book |
| include Document |
| include Printable |
| # ... |
| end |
Another source file creates a Book and calls print_to_screen:
| b = Book.new |
| b.print_to_screen |
According to the company’s bug management application, there is a problem with this code: print_to_screen is not calling the right print method. The bug report doesn’t provide anymore details.
Can you guess which version of print gets called—the one in Printable or the one in Document? Try drawing the chain of ancestors on paper. How can you quickly fix the code so print_to_screen calls the other version of print instead?
You can ask Ruby itself for the ancestors chain of Book:
| Book.ancestors # => [Book, Printable, Document, Object, Kernel, BasicObject] |
If you draw this ancestors chain on your whiteboard, it will look like Figure 5, .
Let’s see how Ruby builds the chain. Because Book doesn’t have an explicit superclass, it implicitly inherits from Object, which in turn includes Kernel and inherits from BasicObject. When Book includes Document, Ruby adds Document to Book’s ancestors chain right above Book itself. Immediately after that, Book includes Printable. Again, Ruby slips Printable in the chain right above Book, pushing up the rest of the chain—from Document upward.
When you call b.print_to_screen, the object referenced by b becomes self, and method lookup begins. Ruby finds the print_to_screen method in Document, and that method then calls other methods—including print. All methods called without an explicit receiver are called on self, so method lookup starts once again from Book (self’s class) and goes up until it finds a method named print. The lowest print in the chain is Printable#print, so that’s the one that gets called.
The bug report hints that the original author of the code intended to call Document#print instead. In real production code, you’d probably want to get rid of this confusion and rename one of the clashing print methods. However, if you just want to solve this quiz, the cheapest way to do it is to swap the order of inclusion of the modules in Book so that Document gets lower than Printable in the ancestors chain:
| module Printable |
| # ... |
| end |
| |
| module Document |
| # ... |
| end |
| |
| class Book |
* | include Printable |
* | include Document |
| |
| ancestors # => [Book, Document, Printable, Object, Kernel, BasicObject] |
| end |
The implicit receiver of ancestors in the previous code is Book, because in a class definition the role of self is taken by the class. The ancestors chain of Book also contains a third method named print—but Bill is not telling you where it is. If you’re curious, you’ll have to find it yourself, maybe with some help from your friend irb.
It’s almost time to go home after an exhausting but very satisfying day of work. But before you call it a day, Bill does a complete wrap-up of what you learned.