`ActiveModel::Conversion`にデフォルトの`#render_in`実装を追加
ActiveModel::Conversionモジュールに#render_inのデフォルト実装が追加され、Active ModelおよびActive Recordのインスタンスがrenderメソッドに渡された際の挙動をモデル側から制御できるようになりました。これにより、ViewComponentなどのサードパーティビューシステムとのインテグレーションポイントがActive Modelレベルで統一されます。
背景
#render_inメソッドは#36388および#37919でAction Viewに導入され、オブジェクトが自身のレンダリングをビューコンテキストに委ねる拡張ポイントとして機能してきました。続く#50623では、locals:やブロックといった呼び出し元のオプションを#render_inに転送する対応が行われています。
しかし、#render_inはあくまで「オプションのインターフェース」であり、ActiveModel::Conversionをincludeしたモデルはデフォルトでは#render_inを持ちませんでした。そのため、render personのような呼び出しではto_partial_path経由のパス解決が内部的に行われるのみで、モデル側がレンダリング処理をカスタマイズする標準的な経路がありませんでした。本PR #57349はこの空白を埋め、Active Modelをincludeするすべてのクラスにデフォルトの#render_in実装を提供します。
技術的な変更
ActiveModel::Conversionのインスタンスメソッドとして#render_inが追加され、to_partial_pathとobject: selfを組み合わせたパーシャルレンダリングがデフォルト実装となりました。
追加されたメソッド(activemodel/lib/active_model/conversion.rb):
def render_in(view_context, **options, &block)
view_context.render(partial: to_partial_path, object: self, **options, &block)
end
シグネチャは#50623で確立されたrender_in(view_context, **options, &block)に準拠しており、呼び出し元から渡されたlocals:などのオプションとブロックがそのままパーシャルレンダリングに引き継がれます。たとえば次のような呼び出しがすべて意図通りに動作します:
person = Person.new(name: "Ralph")
render person # => "Ralph"
render person, shout: true # => "RALPH"
render renderable: person # => "Ralph"
render renderable: person, locals: { shout: true } # => "RALPH"
Active Model Lintテストへの追加(activemodel/lib/active_model/lint.rb):
def test_render_in
view_context = Object.new
def view_context.render(...)
""
end
assert_respond_to model, :render_in
assert_kind_of String, model.render_in(view_context)
end
#render_inへの応答と文字列の返却が、Active ModelのLintテストに正式な要件として組み込まれました。これにより、ActiveModel::Lint::Testsを使ってモデルの適合性を検証しているプロジェクトは、#render_inの存在も自動的に検証されます。
また、ログサブスクライバーのテスト(actionview/test/template/log_subscriber_test.rb)では期待するログ行数が1から2に変更されており、#render_in経由のレンダリングによってRendering Customerのログエントリが新たに出力されることが確認されています。
設計判断
既存のto_partial_pathとの関係を保持したデフォルト実装 が採用されました。
新しいデフォルト実装はpartial: to_partial_pathとobject: selfを組み合わせるだけで、従来のrender personが内部で行っていた処理と等価です。これにより、既存のモデルを使用するアプリケーションは#render_inの追加によって挙動が変わりません。モデル側でカスタムの#render_inを定義している場合はそちらが優先されるため、オーバーライドによるカスタマイズの自由度も維持されています。
#46202ではコントローラやビュー側にrenderer_forのような選択ロジックを持たせる案も議論されていましたが、本PRはその方向には進まず、モデル自身がレンダリング方法を提供するという現行の#render_inの設計を踏襲しています。optionsとブロックの透過的な転送を標準実装に含めることで、locals:やshout: trueのようなコールサイトの引数がパーシャルまで届く設計になっています。
まとめ
本PRは、Action Viewの内部処理として暗黙的に行われていた「to_partial_pathを使ったパーシャルレンダリング」を、Active Modelの公開インターフェースとして明示的に定義した変更です。デフォルト実装を提供しながら後方互換性を保ち、かつLintテストへの追加によってActive Modelの公式な契約としての地位を確立したことで、ViewComponentなどのビューシステムがモデルレベルでのレンダリングカスタマイズを標準的な経路で実装できる基盤が整いました。