結論
captureメソッドとprocを使おう!
経緯
部分テンプレート(partial)はとても便利な機能ですが、使いすぎるとコードが少し散らかってしまいます。
たった1つのViewファイルで使うためだけの場合でも、_foo.html.erb や_bar.html.erbといったファイルが増えてしまいます。それにもかかわらず、これらのファイルはどのViewからも呼び出せてしまいます(スコープが広いです)。
また、複数のファイルを行ったり来たりする手間もかかります。
例えばReactの場合では、単一ファイルの中でコンポーネントを分割することができます。このような機能をRailsのView(erb, haml, slim)でも実装する方法があるので共有します。
実装方法
結論に書いたように、captureメソッドとprocを使います。
captureメソッドについてはRailsガイドのこちらに説明があります。
captureメソッドを使うと、以下のようにテンプレートの一部を抽出して変数にキャプチャできます。
このcaputreメソッドを、procと組み合わせます。
2つを組み合わせて、次のようなコードにします。
<%# --- 定義:UIの断片を Proc オブジェクトに束ねる --- %>
<% row = proc do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
</tr>
<% end %>
<%# --- 呼び出し: capture ヘルパーで Proc を評価・描画する --- %>
<table>
<% @users.each do |user| %>
<%= capture(user, &row) %>
<% end %>
</table>
この方法によって、UIの断片を別ファイルに切り出すことなく、プライベートなpartialを実現できます。
もちろん、次のように複数の引数を渡すことも可能です。
<% row = proc do |user, book| %>
<tr>
<td><%= user.name %></td>
<td><%= book.title %></td>
</tr>
<% end %>
<%= capture(current_user, featured_book, &row) %>
具体例
例えばこのようなerbを書きます。
<h1>Products</h1>
<%# --- 定義:テンプレート断片を Proc に束ねる --- %>
<% render_product = proc do |product| %>
<div class="product">
<h2><%= product.name %></h2>
<p><%= product.description %></p>
<span>¥<%= product.price %></span>
</div>
<% end %>
<%# --- 1回目の呼び出し:通常の一覧表示 --- %>
<div id="products">
<% @products.each do |product| %>
<%= capture(product, &render_product) %>
<% end %>
</div>
<%# --- 2回目の呼び出し:おすすめ商品セクション --- %>
<h2>Recommended Products</h2>
<div id="recommended">
<% @products.select(&:recommended?).each do |product| %>
<%= capture(product, &render_product) %>
<% end %>
</div>
データを用意してCSSをつけると、次の画像のように表示されます。

おわりに
この方法が一般的かは分かりませんが、一つのファイル内でプライベートなpartialを実装したいときに便利です。外部のGemや、複雑な実装をおこなうことなく、RubyとRailsの基本的な機能のみで実現できます。
(もしかしたらもっと良い方法があるかもしれないので、お気軽にコメントください!)
えにしテック内のSlackでtmaedaさんとdarashiさんに頂いたコメントが、この記事のアイデアの元でした。この場を借りて感謝いたします🙏