Module Hookable
In: Hookable.rb

Hookable provides you with the ability to hook into methods. Mix Hookable into a class and you can designate methods that can be hooked into.

High-level interface

Please see Hookable::Wrapper. All other methods are of little interest to you if you want to use the high-level interface.

Low-level interface

A hook is a Proc that gets passed the object the hooked method is bound to, and the list of arguments passed to the hooked method. This Proc can then do whatever it wants to do and call the original method in the process.

The following example demonstrates the low-level interface. All methods used below are documented in Hookable::ClassMethods. state is a Hookable::State object which is documented as well.

 require 'Hookable'

 class Something
   include Hookable

   def test *args
     puts "In Something#test(#{args.inspect})"
   end

   # Allow others to install hooks into test
   hookable :test
 end

 test_wrap1 = Proc.new do |state, *args|
   puts "BEGIN first wrapper around test"
   state.call *args
   puts "END   first wrapper around test"
 end
 test_wrap2 = Proc.new do |state, *args|
   puts "BEGIN second wrapper around test"
   state.call *args
   puts "END   second wrapper around test"
 end

 # Wrap test_wrap1 and test_wrap2 around Something#test
 Something.add_hook :test, test_wrap1
 Something.add_hook :test, test_wrap2

 s = Something.new
 s.test(1,2,3)

 # Temporarily prevent test_wrap1 from being called when Something#test runs
 Something.disable_hook :test, test_wrap1
 s.test(4,5,6)

The output would be:

 BEGIN second wrapper around test
 BEGIN first wrapper around test
 In Something#test([1, 2, 3])
 END   first wrapper around test
 END   second wrapper around test
 BEGIN second wrapper around test
 In Something#test([4, 5, 6])
 END   second wrapper around test

Oddities

  • Your hooks are not bound to the target object. If you created a hook Proc by way of Method#to_proc, it’s actually bound to wherever you got it from.
  • Consider the following situation:
     a = Extender.method(:test_wrap1).to_proc
     b = Extender.method(:test_wrap1).to_proc
     puts a == b
    

    This will return false. Why? Because Ruby creates two different Proc objects when you use Method#to_proc twice. As a result, calling Something.add_hook(:test, a) and then Something.remove_hook(:test, b) will not work.

Classes and Modules

Module Hookable::ClassMethods
Class Hookable::State
Class Hookable::Wrapper

[Validate]