`#render_in` にオプションとブロックを渡せるようにする

rails/rails

render_in を実装したオブジェクトへのレンダリング呼び出しで、locals などのオプションとブロックが渡せるようになりました。これにより、コンポーネントオブジェクトが呼び出し元のコンテキストに応じた柔軟なレンダリングを行えるようになります。

背景

#render_in を介したレンダリングは #36388#37919 で導入されましたが、その実装はオブジェクトがレンダリングに必要な情報をすべて自身で保持していることを前提としていました。そのため、呼び出し元から locals: { ... } のようなオプションを渡す手段がなく、ブロックも無視されていました。

#45432 はビューとコントローラの render でブロックをレンダラブルに渡せるよう試みましたが、オプション(locals など)の伝搬は対応していませんでした。今回の変更はその不足を解消し、オプションとブロックの両方を render_in まで一貫して伝播させます。

技術的な変更

ブロックとオプションを render_in まで届けるために、レンダリングスタック全体を横断する変更が加えられています。

ActionView::RendererTemplateRendererActionView::Rendering#render_to_body / _render_templateAbstractController::Rendering#render_to_body といった各レイヤーのメソッドシグネチャに &block が追加され、ブロックが呼び出し元から ActionView::Template::Renderable まで伝播するようになりました。

ActionView::Template::Renderable の変更が最も中心的です。

変更前:

def render(context, *args)
  @renderable.render_in(context)
rescue NoMethodError
  if !@renderable.respond_to?(:render_in)
    raise ArgumentError, "'#{@renderable.inspect}' is not a renderable object. It must implement #render_in."
  else

変更後:

def initialize(renderable, &block)
  @renderable = renderable
  @block = block
end

def render(context, locals)
  if @renderable.method(:render_in).arity == 1
    ActionView.deprecator.warn <<~WARN
      Action View support for #render_in without options is deprecated.

      Change #render_in to accept keyword arguments.
    WARN

    @renderable.render_in(context, &@block)
  else
    @renderable.render_in(context, locals: locals, &@block)
  end
rescue NameError
  if !@renderable.respond_to?(:render_in)
    raise ArgumentError, "'#{@renderable.inspect}' is not a renderable object. It must implement #render_in."
  else

コンストラクタがブロックを受け取って @block に保持し、render 呼び出し時に locals と合わせて render_in へ委譲するようになりました。

ビュー側の RenderingHelper#render でも、:renderable キーを持つ Hash が渡された場合にブロックを view_renderer.render に透過させるよう条件が修正されています。

変更前:

case options
when Hash
  in_rendering_context(options) do |renderer|
    if block_given?
      view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
    else
      view_renderer.render(self, options)
    end
  end
else
  if options.respond_to?(:render_in)
    options.render_in(self, &block)

変更後:

case options
when Hash
  in_rendering_context(options) do |renderer|
    if block_given? && !options.key?(:renderable)
      view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
    else
      view_renderer.render(self, options, &block)
    end
  end
else
  if options.respond_to?(:render_in)
    view_renderer.render(self, renderable: options, locals: locals, &block)

block_given? の条件に !options.key?(:renderable) が加わり、:renderable を伴うブロック呼び出しがパーシャルとして誤処理されなくなりました。また、レンダラブルオブジェクトが直接渡された場合も render_in を直接呼ぶのをやめ、view_renderer.render 経由でルーティングすることで一貫性を持たせています。

さらに ActionView::Rendering#_normalize_args では、レンダラブルオブジェクトを :renderable キーに格納する際に options[:locals] = options を追加しており、positional引数で渡されたオプションが locals として render_in に届くようになっています。

これらの変更により、以下のような呼び出しがすべて動作するようになりました。

render(Greeting.new)                                        # => "Hello, World"
render(Greeting.new, name: "Local")                         # => "Hello, Local"
render(renderable: Greeting.new, locals: { name: "Local" }) # => "Hello, Local"
render(Greeting.new) { "Hello, Block" }                     # => "Hello, Block"
render(renderable: Greeting.new) { "Hello, Block" }         # => "Hello, Block"

設計判断

既存の #render_in(view_context) シグネチャ(引数1つ)を非推奨化し、キーワード引数を受け取る新シグネチャへの移行を促す方針が採用されました。

Renderable#render 内で method(:render_in).arity == 1 を検査することで、旧シグネチャの実装を検出して非推奨警告を出しつつ動作は維持しています。これにより、既存のレンダラブルオブジェクトが壊れることなく移行期間を設けられます。rescue NoMethodErrorrescue NameError に変更されており、render_in 内部で発生した NameError(例: nil.method(:render_in))をオブジェクトが render_in を持たないと誤判定するバグも同時に修正されています。

ActionController::Renderer#renderrender(...) の forwarding構文に変更されており、ブロックを含む任意の引数を下位の render_to_string に透過的に渡せるようにしています。これは最小限のシグネチャ変更でブロック伝播を実現する方法として採用されています。

まとめ

この変更は、#render_in を使うコンポーネントオブジェクトがレンダリング呼び出し元の locals やブロックを受け取れるようになることで、ViewComponentのようなフレームワークとのより柔軟な統合を可能にします。レンダリングスタック全体に &block を伝播させつつ、既存のシグネチャには非推奨警告を出して後方互換性を維持するという段階的な設計判断は、Rails的な移行パスの手本といえます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
8e0a7481

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)の3部構成が明確に適用されており、非常に分かりやすい構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトやGitHubのPR番号へのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Railsのレンダリング内部に関する深い内容であり、専門知識を持つエンジニアという対象読者に完全に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクション、各パラグラフが「総論→各論」の構造で書かれており、トピックセンテンスが明確です。段落の長さも適切で、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードは、提供されたDiffの内容と正確に一致しています。変更前後のコード対比も効果的です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「#render_in」「arity」「deprecator」など、Railsの文脈における技術用語が正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

レンダリングスタックを横断する変更、シグネチャの非推奨化プロセス、`rescue`句の変更理由など、技術的な説明がすべて正確で論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張はすべてPRのDescriptionやDiff内のコードで裏付けられており、ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#50623)や、言及されている他のPR番号(#36388, #37919, #45432)はすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRのタイトル「Pass render options and block to calls to `#render_in`」の内容を日本語で正確に要約しています。

外部知識の正確性 ⚠ WARNING

PRに記載のない外部知識(LTS、サポート状況など)の不使用

「まとめ」セクションで「ViewComponent」という具体的なフレームワーク名に言及していますが、これはPR情報に直接記載のない外部知識です。ただし、変更の意義を説明するための妥当な例示であり、事実を歪めるものではないためWARNINGとしました。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「非推奨化」といった時間や状態に関する表現は、PRの内容と正確に一致しており、誤解を招く表現はありません。