wisperでオプションの異なる複数のテンポラリリスナーをサクッと登録する

# tl;dr
```rb
Wisper.subscribe do
  [
    [listener1, options1],
    [listener2, options2],
  ].each do |(listener, options)|
    Wisper::TemporaryListeners.registrations <<
      Wisper::ObjectRegistration.new(listener, options)
  end
  # do something
end
```

`1.6.2`で確認してますが`2.0.0.rc1`でも同じコードで動きそうです。

# 動機
EventLogListenerのようなログをとるリスナーを一つ用意して、異なるスコープ、異なるprefixで複数のクラスのイベントを購読したい。

```rb
class ApplicatinController < ActionController::Base
  around_action :subscribe_event_log_listener

  def subscribe_event_log_listener
    listener = EventLogListener.new(some_context)
    list_of_options = [
      {scope: Todos::RegisterTodo, prefix: 'on_register_todo'},
      {scope: Todos::CompleteTodo, prefix: 'on_complete_todo'},
      # ... snip
    ]
    # FIXME どうやってテンポラリリスナーとして登録する？
    yield
  end
end

# listener
class EventLogListener
  def on_register_todo_successful(todo); end
  def on_complete_todo_successful(todo); end
end

# use_case
module Todos
  class RegisterTodo
    include Wisper::Publisher

    attr_reader :content

    def initialize(content)
      @content = content
    end

    def execute
      todo = Todo.new(content: content)
      if todo.save
        return broadcast(:successful, todo)
      end
      broadcast(:failed, todo)
    end
  end

  class CompleteTodo
    include Wisper::Publisher

    attr_reader :todo

    def initialize(todo)
      @todo = todo
    end

    def execute
      if todo.complete
        return broadcast(:successful, todo)
      end
      broadcast(:failed, todo)
    end
  end
end
```

## `Wisper.subscribe` を見る

`Wisper.subscribe`は複数のイベントリスナーを共通のオプションで登録できるが、イベントリスナーを個別のオプションで登録することはできない。

```rb
# 複数のイベントリスナーを共通のオプションで登録する場合。
Wisper.subscribe(listener1, listener2, common_options) do
  # do something
end

# イベントリスナーを複数のオプションで登録する場合、入れ子すると実現できる。
# イベントリスナーのオプションが増えるたびに入れ子を増やしていくのは辛い。
Wisper.subscribe(listener1, options1) do
  Wisper.subscribe(listener2, options2) do
    # do something
  end
end
```
## `Wisper.subscribe` の実装を見る

`Wisper.subscribe`はブロックが渡された場合`Wisper::TemporaryListeners`に処理を委譲している。

```rb:lib/wisper.rb
  def self.subscribe(*args, &block)
    if block_given?
      TemporaryListeners.subscribe(*args, &block)
    else
      GlobalListeners.subscribe(*args)
    end
  end
end
```

`Wisper::TemporaryListeners.subscribe`は自分自身をnewしてsubscribeを呼び出している。

```rb:lib/wisper/temporary_listeners.rb
    def self.subscribe(*listeners, &block)
      new.subscribe(*listeners, &block)
    end

    # ... snip

    def subscribe(*listeners, &block)
      options = listeners.last.is_a?(Hash) ? listeners.pop : {}
      begin
        listeners.each { |listener| registrations << ObjectRegistration.new(listener, options) }
        yield
      ensure
        clear
      end
      self
    end
```

`Wisper::TemporaryListeners#subscribe`のyieldの直前でイベントリスナーをregistrationsに登録している。

```rb:lib/wisper/temporary_listeners.rb
        listeners.each { |listener| registrations << ObjectRegistration.new(listener, options) }
```

`Wisper::TemporaryListeners#registrations`はスレッドローカルな値だから

```rb:lib/wisper/temporary_listeners.rb
    def self.registrations
      new.registrations
    end

    def registrations
      Thread.current[key] ||= Set.new
    end
```

`yield`で呼び出されるブロックの中で`Wisper::TemporaryListeners#registrations`を更新してしまえばよさそう。

## 結論

```rb
# Wisper::TemporaryListeners.subscribe do
Wisper.subscribe do
  [
    [listener1, options1],
    [listener2, options2],
  ].each do |(listener, options)|
    Wisper::TemporaryListeners.registrations <<
      Wisper::ObjectRegistration.new(listener, options)
  end
  # do something
end
```