Rubyで指定したメソッドをフックして特定処理を追加する

 今やっている開発で特定処理のみロギングをしたい、とか特定処理を挟みこみたい場合に簡単に追加や取り外しが出来るようにしたいと思ったので、ちょっとだけメタプロしてみました。
 
 まず、フックするメソッドの実装。
 フックを実装したいクラスにextendする形でHookModuleを実装します。今回はクラスメソッドとして実装して、クラス内からhook_method :hogeとメソッドを明示的に指定すると、関数をoriginal_という名前を付加したエイリアスメソッドを作成し、既存の関数の名前にフック処理を追加した後に、元のメソッドを呼び出すようにしています。
 
 特に意味はないですが、今回はObjectクラスにextendしておいたので、どのクラスに対してもフック処理が任意に追加できるようになります。RailsならActiveRecord::Baseあたりに追加しておけば、色々なモデルにフック処理が追加できて便利です。

module HookModule
    def hook_method(target)
        self.class_eval do
            alias_method "original_#{target.to_s}".to_sym, target
            define_method target do
                p "call hook method for #{target}"
                self.__send__("original_#{target.to_s}")
            end 
        end 
    end 
end
Object.extend HookModule

 次に呼び出し側を記述します。先程のコードをhook_method.rbとして保存したので、requireしてから処理を記述します。

require "hook_method.rb"
class CallHookMethod
    def hoge
        puts "hoge"
    end
    hook_method :hoge
end

m = CallHookMethod.new
m.hoge

 以下が、実行結果です。hogeメソッドを呼び出す際に、先にhook_method内で定義した出力が行われていることが確認できます。

"call hook method for hoge"
hoge

 応用すれば、hook_method :hoge, :inject_methodのように、任意のメソッドを実行させることも可能です。

追記:
 このパターンだと、ブロック、引数がある、なしに対応できないため、if文を使って4つの条件を作る必要があります。引数は*args、ブロックは&blockで取れるので、これを使って4つのパターンに対する__send__を作ればOKです。

 ブロックがないメソッドに対してブロックを送ったりすると__send__呼び出しの地点でエラーが発生します。