Ruby: Module.#prepend でメソッド呼び出しをフック

# はじめに
[Module.#prepend][Ruby#1] でメソッド呼び出しをフックしてみました。

# 試してみる
フック設定はクラス定義に書きたいので、やりやすいように Class クラスに `hook` メソッドを作成してみました。

```rb:hook.rb
module Hook
  def hook(&block)
    module_eval { prepend Module.new &block }
  end
end

class Class
  include Hook
end
```
---
試してみます。

```rb:try1.rb
class C
  hook do                               # hook 設定
    def foo(message)
      puts "hook: #{message}"
      super
    end
  end

  def foo(message)
    puts message
  end
end

obj = C.new
obj.foo("hello")
```

実行結果

```shell-session
$ ruby try1.rb 
hook: hello                # hook の出力
hello
```
---
多段のフックの例。

```rb:try2.rb
class C
  hook do                  # １つ目のhook 設定
    def foo(message)
      puts "#{before_message}: #{message}"
      super
      puts "#{after_message}: #{message}"
    end
  end

  hook do                   # ２つ目のhook 設定
    def foo(message)
      puts "hook2: #{before_message}: #{message}"
      super
      puts "hook2: #{after_message}: #{message}"
    end
  end

  def foo(message)
    puts message
  end

  def before_message
    "before!"
  end

  def after_message
    "after!"
  end
end

c = C.new
c.foo("hello")
```

```shell-session
$ ruby tx.rb 
hook2: before!: hello               # ２つ目のhookの出力
before!: hello                      # １つ目のhookの出力
hello
after!: hello                       # １つ目のhookの出力
hook2: after!: hello                # ２つ目のhookの出力
```

クラス継承ですが、上の「try2.rb」の場合、以下のようになります。
hook 毎に動的に生成した Module インスタンスを継承リストに追加するので、２つ追加されています。

```rb
p C.ancestors   #=> [#<Module:0x007fac4fdb7510>, #<Module:0x007fac4fdb7628>, C, Object, Kernel, BasicObject]
```
---
ロギングに応用した例です。

```rb:try3.rb
require 'forwardable'
require 'logger'

class C
  hook do # ロギング   <-- このコメントが必要なところに'不吉な匂い'が... (「おわりに」で述べます)
    extend Forwardable
    attr_writer :logger
    def logger ; @logger ||= Logger.new(nil) ; end
    delegate %i(debug info warn error fatal) => :logger

    def foo(message)
      info { "call 'foo(#{message})'" }          # ログ出力
      super
    end
    def bar(message)
      info { "call 'bar(#{message})'" }          # ログ出力
      super
    end
  end

  def foo(message)
    puts message
  end

  def bar(message)
    puts message
  end
end

c = C.new
c.logger = Logger.new 'try.log'
c.foo("hello")
c.bar("goodbye")
```
```shell-session
$ ruby try3.rb 
hello
goodbye
```

ログの内容

```text:try.log
# Logfile created on 2015-01-26 00:39:51 +0900 by logger.rb/44203
I, [2015-01-26T00:50:39.131894 #19689]  INFO -- : call 'foo(hello)'
I, [2015-01-26T00:50:39.132019 #19689]  INFO -- : call 'bar(goodbye)'
```

# おわりに
横断的関心事はまとめておくのがよいと思いますが、(私が AOP に詳しくないからか)実用的な例はロギングくらいしか思いつきません。

`hook` メソッドは「クラス定義のスコープに設定を書きたいけど、不必要に識別子(定数名など)は設けたくない」という理由で考えましたが、ロギングのような用途なら具体的名称で設定したほうがいいように思いました。(`ancestors` 等で名前も明示されるようになりますし)

```rb
class C
  module Logging                # 名前(Logging)を付けて、素直にモジュール定義する
    extend Forwardable
    attr_writer :logger
    def logger ; @logger ||= Logger.new(nil) ; end
    delegate %i(debug info warn error fatal) => :logger
         :
  end
  prepend Logging               # prepend する
         :
```
```rb
p C.ancestors  #=> [C::Logging, C, Object, Kernel, BasicObject]
```

`hook`メソッドの必要性は少なそうです。(思いついた時は、いいかなと思ったんですが。。。)

---
本稿内容の動作確認は以下の環境で行っています。

- Ruby 2.1.5 p273

[Ruby#1]: http://docs.ruby-lang.org/ja/2.2.0/method/Module/i/prepend.html