`ActiveSupport::Notifications.subscribe` に `prepend: true` オプションを追加
ActiveSupport::Notifications.subscribe に prepend: true オプションが追加され、サブスクライバーをリストの先頭に登録できるようになりました。これにより、他のサブスクライバーが処理する前にイベントペイロードを変更することが可能になります。
背景
これまで、既存のサブスクライバーより先にイベントペイロードを変更する手段がありませんでした。たとえば sql.active_record イベントの name フィールドを特定の条件下で書き換えたい場合、既存のサブスクライバーの実行順序を制御する方法がなく、モンキーパッチに頼らざるを得ない状況でした。
PRの動機として挙げられているのは、IdentityCache使用時に name を "[IDC] #{name}" のように書き換えるユースケースです。ペイロードの変更が他のサブスクライバーより先に行われることを保証できなければ、意図した変更が他のサブスクライバーの処理に反映されません。prepend: true はこの問題を、実行順序を明示的にコントロールするという形で解決します。
技術的な変更
変更は Notifications モジュール、Fanout クラスの2層にわたり、それぞれに prepend: キーワード引数が追加されています。
ActiveSupport::Notifications のパブリックAPIの変更:
変更前:
def subscribe(pattern = nil, callback = nil, &block)
notifier.subscribe(pattern, callback, monotonic: false, &block)
end
def monotonic_subscribe(pattern = nil, callback = nil, &block)
notifier.subscribe(pattern, callback, monotonic: true, &block)
end
変更後:
def subscribe(pattern = nil, callback = nil, prepend: false, &block)
notifier.subscribe(pattern, callback, monotonic: false, prepend: prepend, &block)
end
def monotonic_subscribe(pattern = nil, callback = nil, prepend: false, &block)
notifier.subscribe(pattern, callback, monotonic: true, prepend: prepend, &block)
end
Fanout#subscribe の内部実装の変更:
prepend: true の場合、@string_subscribers[pattern] に対して << の代わりに unshift を呼び出すことでサブスクライバーをリストの先頭に挿入します。
def subscribe(pattern = nil, callable = nil, monotonic: false, prepend: false, &block)
subscriber = Subscribers.new(pattern, callable || block, monotonic)
@mutex.synchronize do
case pattern
when String
if prepend
@string_subscribers[pattern].unshift(subscriber)
else
@string_subscribers[pattern] << subscriber
end
clear_cache(pattern)
when NilClass, Regexp
if prepend
raise ArgumentError, "Cannot prepend Regex subscribers"
end
@other_subscribers << subscriber
clear_cache
else
Regexpおよびnilパターン(全イベント購読)に対して prepend: true を指定すると ArgumentError が発生します。これは @other_subscribers が文字列パターンとは別に管理されており、Regexp購読は実行順序が保証される構造になっていないためです。
テストでは、通常の順序で登録した :first、:second に加え prepend: true で登録した :prepended が [:prepended, :first, :second] の順で実行されることを確認しています。
設計判断
prepend: true をStringパターン限定とした設計が採用されています。
@string_subscribers はパターン文字列をキーとするHashであり、配列の先頭への挿入(unshift)によって順序を確実に制御できます。一方、@other_subscribers に格納されるRegexpおよびnilパターンのサブスクライバーは、マッチング処理の構造上、同様の順序保証を提供することが困難です。ArgumentError を明示的に発生させることで、サポートされないパターンの使用を早期に検出できるようにしています。
また、prepend: のデフォルト値を false とすることで、既存のすべての呼び出しコードへの影響をゼロに抑えています。
まとめ
本PRは、最小限のAPIの拡張でサブスクライバーの実行順序を制御する手段を提供した変更です。unshift という単純な配列操作と prepend: false のデフォルト値により後方互換性を完全に維持しつつ、モンキーパッチなしにペイロードの前処理が可能になりました。