`render.view_component` ペイロードにビューテンプレートパスを追加
render.view_component イベントのペイロードに view_identifier フィールドが追加され、レンダリングされたテンプレートファイルのパスを取得できるようになりました。これにより、Coverband のようなコードカバレッジツールがビューテンプレートの使用状況を追跡できるようになります。
背景
これまで render.view_component イベントのペイロードには、コンポーネントの .rb ファイルパスを示す identifier のみが含まれていました。コンポーネント自体がレンダリングされたことはわかっても、どのテンプレートファイルが実際に使われたかは追跡できない状態でした。
Coverband のようなツールは、ActiveSupport::Notifications の計装イベントを利用してコードカバレッジを計測します。しかしテンプレートファイルのパス情報がペイロードに含まれないため、.html.erb ファイルが実際に使われているかどうかを検出できないという問題がありました。
この制約を解消するために、テンプレートファイルのパスを保持する view_identifier フィールドがペイロードに追加されました。
技術的な変更
lib/view_component/instrumentation.rb に2つのメソッドが変更・追加され、テンプレートパスをペイロードへ安全に渡す仕組みが実現されました。
変更前:
def render_in(view_context, &block)
return super if !Rails.application.config.view_component.instrumentation_enabled.present?
ActiveSupport::Notifications.instrument(
"render.view_component",
{
name: self.class.name,
identifier: self.class.identifier
}
) do
super
end
end
変更後:
def render_in(view_context, &block)
return super if !Rails.application.config.view_component.instrumentation_enabled.present?
payload = {
name: self.class.name,
identifier: self.class.identifier,
view_identifier: nil
}
ActiveSupport::Notifications.instrument(
"render.view_component",
payload
) do
result = super
payload[:view_identifier] = @__vc_instrumentation_view_identifier
result
end
ensure
@__vc_instrumentation_view_identifier = nil
end
def around_render
result = super
@__vc_instrumentation_view_identifier = current_template&.path
result
end
ポイントは around_render のオーバーライドにあります。Base#render_in の ensure ブロックが @current_template を元に戻す前のタイミングで current_template&.path を取得し、インスタンス変数 @__vc_instrumentation_view_identifier に退避させています。その後、render_in の instrument ブロック内で payload[:view_identifier] にセットすることで、イベントリスナーに渡るペイロードへ確実に書き込まれます。
インライン call メソッドを使うコンポーネント(テンプレートファイルを持たない)では current_template が存在しないため、view_identifier は nil になります。また、render_in の ensure 節で @__vc_instrumentation_view_identifier を nil にリセットすることで、再利用時の状態汚染を防いでいます。
更新後のペイロードは次のようになります:
ActiveSupport::Notifications.subscribe("render.view_component") do |event|
event.payload
# => {
# name: "MyComponent",
# identifier: "/app/components/my_component.rb",
# view_identifier: "/app/components/my_component.html.erb" # インライン call コンポーネントでは nil
# }
end
テスト側では test/sandbox/test/instrumentation_test.rb に1行が追加され、view_identifier が正しいテンプレートパスを返すことが検証されています。
assert_match("app/components/instrumentation_component.html.erb", events[0].payload[:view_identifier])
設計判断
around_render フックを経由してテンプレートパスを取得する設計が採用されました。
current_template は Base#render_in の ensure ブロックでリセットされるため、render_in のブロック内から直接参照しようとしても取得できません。around_render は super(テンプレートのレンダリング)完了直後・ensure 発動前に処理を挿入できる唯一のタイミングです。この位置でパスを中間変数に退避させ、render_in からそれを拾う2段構えの構造が、既存のライフサイクルを壊さずに情報を引き渡す最小限の方法として選ばれています。
また、ペイロードオブジェクトを事前に payload 変数として取り出し、ブロック内で直接ミューテートする実装も注目に値します。ActiveSupport::Notifications.instrument のブロック外でペイロードを変更してもイベントリスナーには反映されませんが、同一オブジェクトへの参照を保持することでブロック内の後段処理として書き込み可能にしています。
まとめ
view_identifier の追加は、render.view_component ペイロードに対する小さな変更ですが、計装の情報密度を高める実用的な改善です。ViewComponent のレンダリングライフサイクルを深く理解したうえで、既存の around_render フックと ensure ブロックの実行順序を巧みに活用した設計は、同様のフック機構を扱う際の参考になります。