Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: パーシャルの意外に知られていない賢い機能(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

参考: §3.4 パーシャルを使う -- レイアウトとレンダリング - Railsガイド

Rails: パーシャルの意外に知られていない賢い機能(翻訳)

パーシャル(partial)はRailsの重要な部品です。概念的にシンプルでわかりやすいだけでなく、実はあまり知られていない賢い機能がいくつも盛り込まれています。そうした機能を残らず見ていきましょう!

🔗 基本的なレンダリング

まずはパーシャルの基本的なレンダリング方法をおさらいしましょう。

<%= render partial: "application/navigation" %>

これを実行すると、app/views/application/_navigation.html.erbに配置されているパーシャルファイルがレンダリングされます。パーシャルファイル名は_で始まらないといけませんが、呼び出しでは_を付けないんでしたよね。

このpartialキーワードは省略してもOKです。

<%= render "application/navigation" %>

このくらいはもうご存知ということでよいですよね。

🔗 locals:でローカル変数を渡せる

パーシャルには以下のようにlocals:で変数を渡せます。

<%= render partial: "user", locals: {user: @user}  %>

partial:のときと同様に、以下のようにlocals:も省略できます。

<%= render "user", user: @user %>

Railsの規約をめいいっぱい活用すれば、さらに以下のように短く書けます。

<%= render @user %>

ここでは以下が満たされていることが前提です。

  • @user変数が存在していること
  • パーシャルファイルがapp/view/users/ディレクトリに_user.html.erbという名前で配置されていること

こんなにすっきり書けるんですね。では、短縮版を使うべき場合とそうでない場合は、どんなときでしょうか?
基本的には短縮版を使うのが普通です(特に渡す変数が1〜2個しかない場合)。
しかし他にも変数を渡す必要が生じたら、以下のように短縮版でない構文が必要になります。

<%= render partial: "user", locals: {admin: @admin}, as: :user %>

🔗 locals:アノテーションでローカル変数を明示的に指定する

これはRails 7.2で導入された機能です。以下のように<%# locals: (変数名:) -%>でパーシャルにアノテーションを追加することで、そのパーシャルでどの変数が必要かを明示できます。

<%# locals: (name:) -%>
<%= name %>

localsアノテーションにはデフォルト値も設定できます。以下の場合、ユーザーに名前がなければ"Stranger"と表示されます。

<%# locals: (name: "Stranger") -%>
<%= name %>

🔗 local_assignsによる暗黙のローカル変数

パーシャルでは「暗黙のローカル変数」も使えます。
上述のuserパーシャルをadminビューでも使いたいときは、以下のようにパーシャルにlocal_assignsを書くことで、ローカル変数のキー(admin?: true)にアクセスできます。

<div>
<p>
  <%= name %>
</p>

<% if local_assigns[:admin?] %>
  <dl>
    <dt>
      Created at:
    </dt>
    <dd>
      <%= user.created_at %>
    </dd>
  </dl>
<% end %>
</div>

このパーシャルを以下のようにadmin?: true付きで呼び出すと、admin用の要素がレンダリングされます。

<%= render "user", user: @user, name: "Cam", admin?: true %>

以下のようにadmin?: trueを付けずに呼び出せば、一般ユーザー向けの要素だけが表示されます。

<%= render "user", user: @user, name: "Cam" %>

パーシャルでlocal_assignsを使わないと、このローカル変数が渡されなかった場合にテンプレートのレンダリングがエラーで失敗してしまいます。

🔗 パーシャルでレイアウトを指定する

パーシャルは「レイアウト」でラップすることも可能です。ただし、app/views/layouts/ディレクトリに置かれているあのレイアウトのことではありませんのでご注意ください。

<%= render partial: "user", layout: "shared/admin" %>

上で指定するadminレイアウトファイルは、app/views/shared/_admin.html.erbに以下のような内容で保存します。

<div class="admin">
  <%= yield %>
</div>

🔗 コレクションをパーシャルでレンダリングする

app/views/users/index.html.erbでユーザーのリスト(コレクション)をレンダリングしたい場合、たとえば以下のように書けます。

<%= render partial: "user", collection: @users %>

さらに短く書くことも可能です。

<%= render @users %>

こうすると、_user.html.erbパーシャルを自動的に参照し、ユーザーのコレクションが@users変数に保存されていると仮定してレンダリングします。

しかし実は、Userのように常に同一のリソースを渡さなければならないという縛りはありません。以下のようにリソースの種類が異なっていても動きます。

<%= render [User.first, Admin.first, Customer.first, User.second] %>

この場合、それぞれのリソースが実際に存在していて、かつ対応するパーシャル(_user.html.erb _admin.html.erb_customer.html.erb)も存在していることが前提です。

さらに、実はActive Recordモデル以外のものも渡せます。以下のようにto_partial_pathに応答するクラスであれば、どんなクラスでも渡せます(Action Packが行う探索はすべてこの規約に基づいています)。

# app/models/login.rb
class Login
  def to_partial_path
    "logins/login" # ここにはどんなパーシャルでも書ける
  end
end

または以下のようにActiveModel::Conversionincludeすることでも使えます。

# app/models/login.rb
class Login
 include ActiveModel::Conversion
end
Login.new.to_partial_path
# => "logins/login"

後者はto_partial_path以外のものも追加します。詳しくはソースコードを参照してください。

🔗 空のステートをレンダリングする

 @usersなどのコレクションが空の場合は、rendernilを返します。これを利用して、コレクションが空であることを以下のように||でシンプルに書けます。

<%= render(@users) || "No users yet..." %>

this technique using CSS

さらに汎用のソリューションについては、以下のようにCSSで行う方法がおすすめです。

参考: Use CSS’ only-child instead of if/else logic | Rails Designer

🔗 コレクションのローカル変数

コレクションのローカル変数を以下のようにas:で変更できます。

<%= render partial: "user", collection: @users, as: :customer %>

これで、_user.html.erbパーシャル内でcustomer.idにアクセス可能になります(どんな属性名でも利用できます)。

コレクションでも以下のように他の変数をlocals:で渡せます。

<%= render partial: "user", collection: @users, locals: {admin?: true} %>

🔗 パーシャルのキャッシュ

Railsのキャッシュはいつもシンプルです。コレクションも以下のようにcached: trueでシンプルにキャッシュできます。

<%= render partial: "user", collection: users, cached: true %>

これによって、ユーザーごとにレンダリングされるパーシャルがキャッシュされます。
試すときはdevelopment環境でbin/rails dev:cacheを実行しておくこと。

🔗 カウンタ変数

<%= user_counter %>でカウンタもレンダリングできます。
このカウンタ名の_より前の部分の名前は、パーシャル名から推測されます。
つまり、_customer.html.erbというパーシャル名なら<%= customer_counter %>になります。

🔗 パーシャルのコレクションのレイアウト

単一リソースのパーシャルの場合と同様に、コレクションのパーシャルにもlayout:で独自のレイアウトを指定できます。

<%= render partial: @users, layout: "users/wrapper" %>

続いて、app/views/users/_wrapper.html.erbというレイアウトファイルに以下のように書けます。

<ul id="users">
  <%= yield %>
</ul>

🔗 スペーサーテンプレート

コレクションのパーシャルにはさらにこんなオプションもあります。
以下のようにspacer_template:でスペーサー用のテンプレートを指定すれば、パーシャルのインスタンスごとにスペーサーが挿入されます。

<%= render partial: @users, spacer_template: "user/divider" %>

上の場合、app/views/users/_divider.html.erbというパーシャルがスペーサーとして読み込まれます。

🔗 ボーナス: パーシャルをコンポーネントのように扱う技

ViewComponentを使ったことがあるのに、プロジェクトで利用できない事情がおありの方は、以下のようにすればパーシャルをまるでViewComponentのように扱えます。

まずはapp/view/components/フォルダ内にコンポーネントとしてのパーシャルを作成します。ここでは_card.html.erbとします。

<section class="card">
  <h4 class="card__header">
    <%= title %>
  </h4>

  <div class="card__body">
    <%= yield %>
  </div>
</section>

続いて、ビューで以下のように呼び出します。

<%= render layout: "components/card", locals: { title: "User Profile" } do %>
  <p>Just some text for this user</p>
<% end %>

しかしおそらく、以下のようなヘルパーを作って呼び出しをもっと短くしたくなるでしょう。

# app/helpers/components_helper.rb
module ComponentsHelper
  def component(name, **attributes, &)
    render partial: "components/#{name}", layout: "components/#{name}", locals: attributes, &
  end
end

このComponentsHelperApplicationHelperincludeすれば、以下のようにcomponentメソッドでコンポーネントらしく呼び出せるようになります。

<%= component "card", title: "User Profile" do %>
  <p>Just some text for this user</p>
<% end %>

上述の明示的なローカル変数も併用すれば、依存関係の生じないコンポーネント的なセットアップのできあがりです。

パーシャルの意外に知られていない賢い機能は以上です。あなたが知らなかった機能はどれでしたか?

訳注

ComponentHelperについては以下の続編記事で詳しく扱われています。

Railsのコンポーネントを「gemなしで」シンプルに構築する(翻訳)

関連記事

Railsのコンポーネントを「gemなしで」シンプルに構築する(翻訳)

実践ViewComponent(1): 現代的なRailsフロントエンド構築の心得(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。