RubyでメソッドをフックしてBefore/Afterにログを付けたりする

## 概要
最近読んだ[Ruby: Module.#prepend でメソッド呼び出しをフック](http://qiita.com/lnznt/items/5653f04749c9f82f7021)という記事の手法をベースにメソッドをフックしてログを仕込んだりする手法を考えてみます。

この記事の手法を利用すると、任意のクラスの任意のメソッドをフックして、前後に処理を挿入することができます。
フックは好きな順番、好きな個数を設定できます。

人によっては、これをAOP(アスペクト指向プログラミング)のような何かだと考えるかもしれません。

## 実装手順
1. Objectを拡張して、フックを簡単に追加できるようにする。
2. フックする処理を記述するモジュール(以降、フックモジュールと呼ぶ)を定義する。
3. フックモジュールをObjectに登録し、任意のクラスにフック処理を追加できるようにする。
4. フックしたい対象のクラスに、フックしたいメソッドを指定した記述を追加する。

### 1. Objectにフック追加メソッドを定義
- Objectクラスにフックモジュールを登録する特異メソッド`register_hook`を定義する。
    - `regist`とか書くと原理主義者の邪教徒狩りに合うので`register`と書きます。
- `register_hook`は「フックモジュールを`include`し、引数で渡されたメソッドをフックモジュールに登録する」モジュールを生成するメソッドを定義する(・・・ややこしい)。

```ruby
class << Object
  def register_hook(hook_module)
    define_method('hook_' + hook_module.name) do |methods|
      Module.new do |m|
        include hook_module
        hook_module.register methods
      end
    end
  end
end
```


### 2. フックモジュールを定義
フックモジュールは以下の要件を満たす。

- `register`という特異メソッドを持つ。
- `register`はシンボルの配列を受け取り、シンボルで与えられる名前のメソッドを定義する。
- `register`で定義されたメソッドは、シンボルで与えられる名前の元のメソッドを呼び出す。
- その他、細かいことは以下のサンプルを参照。

```ruby
# メソッドの呼び出し前後にログを仕込むフックモジュール
module StartEndLogger
  def self.register(methods)
    methods.each do |method|
      define_method(method) do |*args|
        begin
          log method, 'start >>> '
          super(*args) # フックしたメソッドの呼び出し
        ensure
          log method, 'end <<< '
          puts
        end
      end
    end
  end
  def log(method, mark)
    msg = mark
    msg << self.to_s
    msg << '.' << method.to_s
    puts msg
  end
end
```


### 3. フックモジュールをObjectに登録
Objectクラスで`register_hook`を呼び出し、フックモジュールを登録する。
これで、任意のクラスでフック処理を追加できるようになる。

```ruby
class << Object; register_hook(StartEndLogger) end
```

### 4. 対象クラスにフック処理を追加
- `regisiter_hook`によって生成されたメソッド(ここでは`hook_StartEndLogger`)を呼び出し、生成されたモジュールを`prepend`する。
- `hook_StartEndLogger`にはフック対象のメソッド名をシンボルとして渡す。

```ruby
class Widget
  prepend hook_StartEndLogger %i(introduce_me rename sum)

  def initialize(name); @name = name end
  def introduce_me; puts "I'm a #{@name}" end
  def rename(name); @name = name end
  def sum(*args); args.reduce(:+) end
end
```


## 使用例
実際に実行してみます。

```ruby
w = Widget.new 'Freezer'
w.introduce_me
w.sum 1, 2, 3
w.rename 'Cooler'
w.introduce_me

# => start >>> #<Widget:0x1940ac0>.introduce_me
# => I'm a Freezer
# => end <<< #<Widget:0x1940ac0>.introduce_me
# => 
# => start >>> #<Widget:0x1940ac0>.sum
# => end <<< #<Widget:0x1940ac0>.sum
# => 
# => start >>> #<Widget:0x1940ac0>.rename
# => end <<< #<Widget:0x1940ac0>.rename
# => 
# => start >>> #<Widget:0x1940ac0>.introduce_me
# => I'm a Cooler
# => end <<< #<Widget:0x1940ac0>.introduce_me
```



## おまけ:複数のフックを追加
フック処理は複数追加できます。
ログを勝手に取るだけではシツレイなので、アイサツを追加します。
アイサツは大事。イイネ？

```ruby
# メソッド呼び出し前に挨拶
module Greeter
  def self.register(methods)
    methods.each do |method|
      define_method(method) do |*args|
        puts "Domo #{method}=san, Hook-module desu."
        super(*args)
      end
    end
  end
end
# Greeterを全クラスで登録可能にする
class << Object; register_hook(Greeter) end


# フック対象
class Widget
  prepend hook_StartEndLogger %i(introduce_me rename sum)
  prepend hook_Greeter %i(introduce_me sum)

  def initialize(name); @name = name end
  def introduce_me; puts "I'm a #{@name}" end
  def rename(name); @name = name end
  def sum(*args); args.reduce(:+) end
end
```

事項結果がこちら。

```ruby
w = Widget.new 'Freezer'
w.introduce_me
w.sum 1, 2, 3
w.rename 'Cooler'
w.introduce_me

# => ドーモ introduce_me=サン, Greeterデス。
# => start >>> #<Widget:0x19906e0>.introduce_me
# => I'm a Freezer
# => end <<< #<Widget:0x19906e0>.introduce_me
# => 
# => ドーモ sum=サン, Greeterデス。
# => start >>> #<Widget:0x19906e0>.sum
# => end <<< #<Widget:0x19906e0>.sum
# => 
# => 6
# => start >>> #<Widget:0x19906e0>.rename
# => end <<< #<Widget:0x19906e0>.rename
# => 
# => ドーモ introduce_me=サン, Greeterデス。
# => start >>> #<Widget:0x19906e0>.introduce_me
# => I'm a Cooler
# => end <<< #<Widget:0x19906e0>.introduce_me
```