Книга: Metaprogramming Ruby 2
Назад: What Happens When You Call a Method?
Дальше: Wrap-Up

Quiz: Tangle of Modules

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?

Quiz Solution

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, .

images/chp1_tangle_quiz.jpg

Figure 5. The ancestors chain of the Book class

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.

Назад: What Happens When You Call a Method?
Дальше: Wrap-Up