Книга: Metaprogramming Ruby 2
Назад: Quiz: Checked Attributes (Step 4)
Дальше: Quiz: Checked Attributes (Step 5)

Hook Methods

Where you get one of Bill’s thorough lessons in advanced coding.

The object model is an eventful place. Lots of things happen there as your code runs: classes are inherited, modules are mixed into classes, and methods are defined, undefined, and removed. Imagine if you could “catch” these events like you catch GUI mouse-click events. You’d be able to execute code whenever a class is inherited or whenever a class gains a new method.

Well, it turns out you can do all these things. This program prints a notification on the screen when a class inherits from String:

 
class​ String
 
def​ self.inherited(subclass)
 
puts ​"​#{self}​ was inherited by ​#{subclass}​"
 
end
 
end
 
 
class​ MyString < String; ​end
<= 
String was inherited by MyString

The inherited method is an instance method of Class, and Ruby calls it when a class is inherited. By default, Class#inherited does nothing, but you can override it with your own code as in the earlier example. A method such as Class#inherited is called a Spell: because you can use it to hook into a particular event.

More Hooks

Ruby provides a motley bunch of hooks that cover the most newsworthy events in the object model. Just as you override Class#inherited to plug into the lifecycle of classes, you can plug into the lifecycle of modules by overriding Module#included and (in Ruby 2.0) Module#prepended:

 
module​ M1
 
def​ self.included(othermod)
 
puts ​"M1 was included into ​#{othermod}​"
 
end
 
end
 
 
module​ M2
 
def​ self.prepended(othermod)
 
puts ​"M2 was prepended to ​#{othermod}​"
 
end
 
end
 
 
class​ C
 
include M1
 
prepend M2
 
end
<= 
M1 was included into C
 
M2 was prepended to C

You can also execute code when a module extends an object by overriding Module#extended. Finally, you can execute method-related events by overriding Module#method_added, method_removed, or method_undefined.

 
module​ M
 
def​ self.method_added(method)
 
puts ​"New method: M#​#{method}​"
 
end
 
 
def​ my_method; ​end
 
end
<= 
New method: M#my_method

These hooks only work for regular instance methods, which live in the object’s class. They don’t work for Singleton Methods (), which live in the object’s singleton class. To catch Singleton Method events, you can use BasicObject#singleton_method_added, singleton_method_removed, and singleton_method_undefined.

Module#included is probably the most widely used hook, thanks to a common metaprogramming spell that’s worthy of an example of its own.

The VCR Example

VCR is a gem that records and replays HTTP calls. The Request class in VCR includes a Normalizers::Body module:

 
module​ VCR
 
class​ Request ​#...
 
include Normalizers::Body
 
#...

The Body module defines methods that deal with an HTTP message body, such as body_from. After the include, those methods become class methods on Request. Yes, you read that right: Request is gaining new class methods by including Normalizers::Body. But a class usually gets instance methods by including a module—not class methods. How can a mixin like Normalizers::Body bend the rules and define class methods on its includer?

Look for the answer in the definition of the Body module itself:

 
module​ VCR
 
module​ Normalizers
 
module​ Body
 
def​ self.included(klass)
 
klass.extend ClassMethods
 
end
 
 
module​ ClassMethods
 
def​ body_from(hash_or_string)
 
# ...

The code above pulls off a convoluted trick. Body has an inner module named ClassMethods that defines body_from and other methods as regular instance methods. Body also has an included Hook Method (). When Request includes Body, it triggers a chain of events:

As a result, body_from and other instance methods get mixed into the singleton class of Request, effectively becoming class methods of Request. How’s that for a complicated code concoction?

This ClassMethods-plus-hook idiom used to be quite common, and it was used extensively by the Rails source code. As you’ll see in Chapter 10, , Rails has since moved to an alternate mechanism—but you can still find examples of the idiom in VCR and other gems.

Назад: Quiz: Checked Attributes (Step 4)
Дальше: Quiz: Checked Attributes (Step 5)