インラインパーシャルでのオブジェクトレンダリング時にローカル変数が消失するバグを修正
Jbuilderのインラインパーシャル構文でオブジェクトをレンダリングする際、カスタムローカル変数が無視される(またはエラーになる)バグが修正されました。コレクションでは正常に動作していたものの、単一オブジェクトのレンダリングパスにのみ存在していた問題です。
背景
json.key object, partial: 'path/to/partial', as: :sym, my_local: 'value' という構文でローカル変数を渡した場合、コレクションと単一オブジェクトで挙動が一致しないという不整合が発生していました。コレクション(配列)では my_local が正しく 'value' として渡される一方、単一オブジェクトでは渡したはずの値が無視されてデフォルト値が使われたり、strict localsでデフォルト値がない場合は missing local エラーが発生したりしていました。
PRの説明によれば、この問題は #591 のパフォーマンス最適化がリグレッションを引き起こした可能性があります。#591 では reverse_merge! を []= に置き換える変更が行われましたが、その際に locals キーの処理タイミングに関する副作用が生じたと考えられます。
根本原因は _set_inline_partial と _render_partial_with_options の責務の重複にありました。_set_inline_partial が先に options[:locals] を設定してしまうため、_render_partial_with_options が後からローカル変数を options から取り出して locals にマージしようとした際、キーが既に存在するためスキップされていました。
技術的な変更
修正の核心は、_set_inline_partial での options[:locals] への書き込みを廃止し、object の受け渡しを options の最上位キーに移すことで、ローカル変数の組み立てを _render_partial_with_options に一本化した点にあります。
変更前:
def _set_inline_partial(name, object, options)
# ...
else
_scope do
options[:locals] = { options[:as] => object } # locals を早期に上書き
_render_partial_with_options options
end
end
end
def _render_partial_with_options(options)
# ...
else
_render_partial options # 別メソッドへの委譲
end
end
def _render_partial(options) # 単一オブジェクト用の分離メソッド
options[:locals][:json] = self
@context.render options
end
変更後:
def _set_inline_partial(name, object, options)
# ...
else
_scope do
options[options[:as]] = object # locals ではなくオプションの最上位キーに設定
_render_partial_with_options options
end
end
end
def _render_partial_with_options(options)
# ...
else
options[:locals][:json] = self # locals への書き込みはここで一元化
@context.render options
end
end
# _render_partial メソッドは削除
変更後は _set_inline_partial が options[:locals] に触れなくなったため、options に含まれる my_local: 'custom' などのカスタムキーが _render_partial_with_options の処理フローで正しく locals に組み込まれます。あわせて _render_partial という中間メソッドも削除され、単一オブジェクトとコレクション以外のレンダリングパスが一本化されました。
テストでは、従来カバーされていなかった3つのケースが追加されています。
- 単一オブジェクトをインラインパーシャルで描画するケース
- 単一オブジェクトにローカル変数を渡すケース
- コレクションにローカル変数を渡すケース
設計判断
ローカル変数の組み立て責務を _render_partial_with_options に集約するアプローチが採用されました。
修正以前は _set_inline_partial が locals ハッシュを生成・設定し、_render_partial_with_options がそれを参照するという分散した構造になっていました。この責務の分散がバグの温床となっていたため、_set_inline_partial はオブジェクトを options の最上位キーに置くだけに留め、locals の組み立ては _render_partial_with_options 内のみで行うよう整理されています。
PR著者は将来的な改善として _set_inline_partial メソッド自体の廃止も提案しており、locals の調整を _render_partial_with_options に完全統合することで間接参照をさらに減らせると述べています。今回の修正はその方向への第一歩と見ることができます。
まとめ
本PRは、単一オブジェクトのインラインパーシャルレンダリングでローカル変数が失われる不整合を、_set_inline_partial と _render_partial_with_options の責務境界を明確化することで解消しました。中間メソッドの削除と責務の集約という設計整理が、バグ修正と同時にコードの見通しを改善しています。