1 Action Viewについて

RailsにおけるWebリクエストは、Action ControllerとAction Viewで扱われます。通常、Action Controllerは、データベースとのやりとりや、必要に応じたCRUD(Create/Read/Update/Delete)アクションの実行に関与します。Action View はその後レスポンスを実際のWebページにまとめる役割を担います。

Action Viewのテンプレートは、HTMLタグの間にERB(Embedded Ruby)を含む形式で書かれます。ビューテンプレートがコードの繰り返しでうずまって乱雑になるのを避けるために、フォーム・日付・文字列に対して共通の動作を提供するヘルパークラスが多数用意されています。アプリケーションの機能向上に応じて独自のヘルパーを追加することも簡単にできます。

Action Viewの一部の機能はActive Recordと結びついていますが、Action ViewがActive Recordに依存しているわけではありません。Action Viewは独立したパッケージであり、任意のRubyライブラリと組み合わせて利用できます。

2 Action ViewをRailsで使う

アプリケーションのapp/viewsディレクトリには、1つのコントローラごとに1つのディレクトリが作成され、そこにビューテンプレートファイルが置かれます。このビューテンプレートはそのコントローラと関連付けられています。これらのファイルは、コントローラ内にあるアクションごとにレンダリング(画面への出力)された結果をビューで表示するために使われます。

scaffoldでリソースを生成するときに、Railsがデフォルトでどんなことを行なうのか見てみましょう。

$ bin/rails generate scaffold article
      [...]
      invoke  scaffold_controller
      create    app/controllers/articles_controller.rb
      invoke    erb
      create      app/views/articles
      create      app/views/articles/index.html.erb
      create      app/views/articles/edit.html.erb
      create      app/views/articles/show.html.erb
      create      app/views/articles/new.html.erb
      create      app/views/articles/_form.html.erb
      [...]

Railsのビューには命名規則があります。上で生成されたファイルを見るとわかるように、ビューテンプレートファイルは基本的にコントローラのアクションと関連付けられています。 たとえば、articles_controller.rbコントローラのindexアクションは、app/views/articlesディレクトリのindex.html.erbを使います。

これらのERBファイルに、それらをラップするレイアウトテンプレートや、ビューから参照されるあらゆるパーシャル(部分テンプレート)を組み合わせることで完全なHTMLが生成され、クライアントに送信されます。この後、本ガイドではこれらの3つの要素について詳しく説明します。

前述のとおり、Railsがレンダリングする最終的なHTMLは「テンプレート」「パーシャル」「レイアウト」の3つの要素から構成されます。 これらについて簡単に説明します。

3 テンプレート

Action Viewのテンプレートはさまざまな方法で記述できます。テンプレートの拡張子が.erbであれば、ERB(Rubyのコードはここに含まれます)とHTMLを記述します。テンプレートの拡張子が.builderであれば、Builder::XmlMarkupライブラリが使われます。

Railsでは複数のテンプレートシステムがサポートされており、テンプレートファイルの拡張子で区別されます。たとえば、ERBテンプレートシステムを使うHTMLファイルの拡張子は.html.erbになります。

3.1 ERB

ERBテンプレートの内部では、<% %>タグや<%= %>タグの中にRubyコードを書けます。 最初の<% %>タグはその中に書かれたRubyコードを実行しますが、実行結果はレンダリングされません。条件文やループ、ブロックなどレンダリングの不要な行はこのタグの中に書くとよいでしょう。 次の<%= %>タグでは実行結果がWebページにレンダリングされます。

以下は、名前をレンダリングするためのループです。

<h1>Names of all the people</h1>
<% @people.each do |person| %>
  Name: <%= person.name %><br>
<% end %>

ループの開始行と終了行は通常のERBタグ(<% %>)に書かれており、名前をレンダリングする行はレンダリング用のERBタグ(<%= %>)に書かれています。上のコードは、単にERBの書き方を説明しているだけではありません。Rubyでよく使われるprintputsのような通常のレンダリング関数はERBでは利用できませんのでご注意ください。以下のコードは誤りです。

<%# 誤り %>
Hi, Mr. <% puts "Frodo" %>

なお、Webページへのレンダリング結果の冒頭と末尾からホワイトスペースを取り除きたい場合は、<%- および -%>を通常の<% および %>と使い分けてください(訳注: これは英語のようなスペース分かち書きを行なう言語向けのノウハウです)。

3.2 Builder

BuilderテンプレートはERBの代わりに利用できる、よりプログラミング向きな記法です。これ特にXMLコンテンツを生成するときに便利です。テンプレートの拡張子を.builderにすると、xmlという名前のXmlMarkupオブジェクトが自動で利用できるようになります。

基本的な例を以下にいくつか示します。

xml.em("emphasized")
xml.em { xml.b("emph & bold") }
xml.a("A Link", "href" => "https://rubyonrails.org")
xml.target("name" => "compile", "option" => "fast")

上のコードから以下が生成されます。

<em>emphasized</em>
<em><b>emph &amp; bold</b></em>
<a href="https://rubyonrails.org">A link</a>
<target option="fast" name="compile" />

ブロックを伴うメソッドはすべて、ブロックの中にネストしたマークアップを含むXMLマークアップタグとして扱われます。以下の例で示します。

xml.div {
  xml.h1(@person.name)
  xml.p(@person.bio)
}

上のコードの出力は以下のようになります。

<div>
  <h1>David Heinemeier Hansson</h1>
  <p>A product of Danish Design during the Winter of '79...</p>
</div>

以下はBasecampで実際に使われているRSS出力コードをそのまま引用したものです。

xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  xml.channel do
    xml.title(@feed_title)
    xml.link(@url)
    xml.description "Basecamp: Recent items"
    xml.language "en-us"
    xml.ttl "40"

    for item in @recent_items
      xml.item do
        xml.title(item_title(item))
        xml.description(item_description(item)) if item_description(item)
        xml.pubDate(item_pubDate(item))
        xml.guid(@person.firm.account.url + @recent_items.url(item))
        xml.link(@person.firm.account.url + @recent_items.url(item))
        xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
      end
    end
  end
end

3.3 Jbuilder

JbuilderはRailsチームによってメンテナンスされているgemの1つで、RailsのGemfileにデフォルトで含まれています。

JbuilderはBuilderと似ていますが、XMLではなくJSONを生成するのに使われます。

Jbuilderが導入されていない場合は、Gemfileに以下を追加できます。

gem 'jbuilder'

.jbuilderという拡張子を持つテンプレートでは、jsonという名前のJbuilderオブジェクトが自動的に利用できるようになります。

基本的な例を以下に示します。

json.name("Alex")
json.email("alex@example.com")

上のコードから以下のJSONが生成されます。

{
  "name": "Alex",
  "email": "alex@example.com"
}

この他のコード例や詳しい情報についてはJbuilder documentationを参照してください。

3.4 テンプレートをキャッシュする

Railsは、デフォルトでビューの各テンプレートをコンパイルしてレンダリング用メソッドにします。developmentモードの場合、ビューテンプレートが変更されるとファイルの更新日時で変更が検出され、再コンパイルされます。

4 パーシャル

パーシャル(部分テンプレート)は、レンダリング処理を扱いやすく分割する仕組みです。パーシャルを使うことで、ビュー内のコードをいくつものファイルに分割して書き出し、他のテンプレートでも使い回せるようになります。

4.1 パーシャルをレンダリングする

パーシャルをビューの一部に含めてレンダリングするには、ビューでrenderメソッドを使います。

<%= render "menu" %>

上の呼び出しにより、_menu.html.erbという名前のファイルの内容が、renderメソッドを書いたその場所でレンダリングされます。パーシャルファイル名の冒頭にアンダースコア(_)が付いていることにご注目ください。これは通常のビューと区別するために付けられています。

ただしrenderで呼び出す際にはこのアンダースコアは不要です。以下のように、他のフォルダの下にあるパーシャルを呼び出す際にもアンダースコアは不要です。

<%= render "application/menu" %>

上のコードは、その位置にapp/views/application/_menu.html.erbパーシャルを読み込みます。

4.2 パーシャルを活用してビューを簡潔に保つ

すぐに思い付くパーシャルの利用法といえば、パーシャルをサブルーチンと同等とみなすというのがあります。ビューの詳細部分をパーシャルに移動し、コードの見通しを良くするために、パーシャルを使うのです。たとえば、以下のようなビューがあるとします。

<%= render "application/ad_banner" %>

<h1>Products</h1>

<p>私たちの素晴らしい製品のいくつかをご紹介します:</p>
<% @products.each do |product| %>
  <%= render partial: "product", locals: { product: product } %>
<% end %>

<%= render "application/footer" %>

上のコードの_ad_banner.html.erbパーシャルと_footer.html.erbパーシャルに含まれるコンテンツは、アプリケーションの多くのページと共有できます。あるページを開発中、パーシャルの部分については詳細を気にせずに済みます。

ビューのパーシャルは、テンプレートやレイアウトと同じテンプレートの継承に依存しているので、ApplicationControllerから継承したコントローラでレンダリングするテンプレートは、app/views/applicationで宣言されたビューパーシャルを表示できるようになります。

コントローラでは、パーシャルを継承チェインで解決するだけでなく、デフォルトのパーシャルも継承チェインでオーバーライドできます。 たとえば、ProductsControllerApplicationControllerを継承すると、<%= render "ad_banner" %>を呼び出したときに、最初にapp/views/products/_ad_banner.html.erbを検索し、見つからない場合はapp/views/application/_ad_banner.html.erbにフォールバックします。

5 renderlocalsオプションを渡す

パーシャルをレンダリングするときには、locals:オプションで渡された個別のキーを、そのパーシャル内のローカル変数として利用可能になります。

<%# app/views/products/show.html.erb %>

<%= render partial: "products/product", locals: { product: @product } %>

<%# app/views/products/_product.html.erb %>

<%= tag.div id: dom_id(product) do %>
  <h1><%= product.name %></h1>
<% end %>

locals:オプションの一部としてビューに渡されていない変数をテンプレートが参照すると、テンプレートでActionView::Template::Errorエラーが発生します。

<%# app/views/products/_product.html.erb %>

<%= tag.div id: dom_id(product) do %>
  <h1><%= product.name %></h1>

  <%# => ActionView::Template::Errorが発生する %>
  <% related_products.each do |related_product| %>
    <%# ... %>
  <% end %>
<% end %>

5.1 local_assignsを使う

locals:オプションに渡した個別のキーは、以下のようにlocal_assignsヘルパーメソッドを使うことでパーシャルのローカル変数としてアクセスできるようになります。

<%# app/views/products/show.html.erb %>

<%= render partial: "products/product", locals: { product: @product } %>
<%# app/views/products/_product.html.erb %>

<% local_assigns[:product]          # => "#<Product:0x0000000109ec5d10>" %>
<% local_assigns[:options]          # => nil %>

local_assignsHashなので、Ruby 3.1のパターンマッチング代入演算子と互換性があります。

local_assigns => { product:, **options }
product # => "#<Product:0x0000000109ec5d10>"
options # => {}

パーシャルのHashローカル変数に:product以外のキーを複数代入する場合、以下のようにsplat演算子**でヘルパーメソッド呼び出しに展開して渡せます。

<%# app/views/products/_product.html.erb %>

<% local_assigns => { product:, **options } %>

<%= tag.div id: dom_id(product), **options do %>
  <h1><%= product.name %></h1>
<% end %>
<%# app/views/products/show.html.erb %>

<%= render "products/product", product: @product, class: "card" %>
<%# => <div id="product_1" class="card">
  #      <h1>A widget</h1>
  #    </div>
%>

パターンマッチングの代入では、変数のリネームもサポートされます。

local_assigns => { product: record }
product             # => "#<Product:0x0000000109ec5d10>"
record              # => "#<Product:0x0000000109ec5d10>"
product == record   # => true

local_assignsHashのインスタンスを返すので、以下のように変数を条件付きで読み出して、locals:オプションにないキーの場合はデフォルト値にフォールバックできます。

<%# app/views/products/_product.html.erb %>

<% local_assigns.fetch(:related_products, []).each do |related_product| %>
  <%# ... %>
<% end %>

Ruby 3.1のパターンマッチング代入演算子にHash#with_defaults呼び出しを組み合わせると、パーシャルのローカル変数のデフォルト値代入を以下のようにコンパクトに書けます。

<%# app/views/products/_product.html.erb %>

<% local_assigns.with_defaults(related_products: []) => { product:, related_products: } %>

<%= tag.div id: dom_id(product) do %>
  <h1><%= product.name %></h1>

  <% related_products.each do |related_product| %>
    <%# ... %>
  <% end %>
<% end %>

5.2 partiallocalsオプションのないrender

上の例では、renderpartiallocalsの2つのオプションを取っています。しかし、渡したいオプションが他にない場合は、これらのオプションを省略できます。

次の例で説明します。

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

上のコードは以下のようにも書けます。

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

5.3 asobjectオプション

ActionView::Partials::PartialRendererは、デフォルトでテンプレートと同じ名前を持つローカル変数の中に自身のオブジェクトを持ちます。以下のコードを見てみましょう。

<%= render partial: "product" %>

上のコードでは、_productパーシャルはローカル変数productから@productを取得できます。これは以下のコードと同等の結果になります。

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

objectオプションは、パーシャルで出力するオブジェクトを直接指定したい場合に使います。これは、テンプレートのオブジェクトが他の場所(別のインスタンス変数や別のローカル変数など)にある場合に便利です。

たとえば、以下のコードがあるとします。

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

上のコードは以下のようになります。

<%= render partial: "product", object: @item %>

asオプションを使うと、ローカル変数に異なる名前を指定できます。たとえば、productではなくitemにしたい場合は次のようにします。

<%= render partial: "product", object: @item, as: "item" %>

上は以下と同等です。

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

5.4 コレクションをレンダリングする

テンプレート上にコレクションを1つ表示し、サブテンプレートでそのコレクションの要素を1つずつレンダリングするというのは、よく行われるパターンです。このパターンは1つのメソッドだけで実行できます。このメソッドは配列を受け取り、配列内の各要素ごとにパーシャルを出力します。

すべての製品(products)を出力するコード例は以下のようになります。

<% @products.each do |product| %>
  <%= render partial: "product", locals: { product: product } %>
<% end %>

上のコードは以下のように1行で書けます。

<%= render partial: "product", collection: @products %>

コレクションを渡してパーシャルが呼び出されると、パーシャルの各インスタンスは、パーシャル名に基づいた変数を経由してレンダリングされるコレクションのメンバーにアクセスします。このパーシャルは_productという名前なので、productを指定すれば、レンダリングされるインスタンスを取得できます。

コレクションのレンダリングにはショートハンド記法があります。@productsProductインスタンスのコレクションであれば、以下のコードでも同じ結果を得られます。

<%= render @products %>

使われるパーシャル名は、コレクションの中にある「モデル名」を参照して決定されます。この場合のモデル名はProductです。作成するコレクションの各要素が不揃い(訳注: 要素ごとにモデルが異なる場合を指します)であっても、Railsはコレクションのメンバごとに適切なパーシャルを選んでレンダリングします。

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

:spacer_templateオプションを使うと、メインのパーシャルの間を埋める第2のパーシャルを指定できます。

<%= render partial: @products, spacer_template: "product_ruler" %>

メインの_productパーシャルの間に、スペーサーとなる_product_rulerパーシャルをレンダリングします(_product_rulerにはデータを渡していません)。

5.6 厳密なlocals

デフォルトでは、テンプレートはキーワード引数として任意のlocalsを受け取れます。テンプレートが受け取ってよいlocalsを定義するには、以下のようにlocalsマジックコメントを追加します。

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

localsにはデフォルト値も設定できます。

<%# locals: (message: "Hello, world!") -%>
<%= message %>

以下のように、localsを完全に無効にすることも可能です。

<%# locals: () %>

5.7 レイアウト

Railsにおける「レイアウト」は、多くのコントローラのアクションにわたって共通して利用できるテンプレートのことです。Railsアプリケーションには必ず全体用のレイアウトがあり、ほぼすべてのWebページ出力はこの全体レイアウトの内側で行われますが、これが典型的なレイアウトです。

たとえば、あるWebサイトにはユーザーログイン用のレイアウトが使われていたり、別のWebサイトにはマーケティングやセールス用のレイアウトが使われていたりします。ログインしたユーザー向けのレイアウトであれば、ナビゲーションツールバーをページのトップレベルに表示し、多くのコントローラやアクションで共通して利用できるようにするでしょう。SaaSアプリケーションにおけるセールス用のレイアウトであれば、トップレベルのナビゲーションに「お値段」や「お問い合わせ先」を共通して表示するでしょう。レイアウトごとに異なる外観を設定してこれらを使い分けることができます。 レイアウトについて詳しくは、ビューのレイアウトとレンダリング ガイドを参照してください。

5.8 パーシャルレイアウト

パーシャルに独自のレイアウトを適用できます。パーシャル用のレイアウトは、アクション全体にわたるグローバルなレイアウトとは異なりますが、動作は同じです。

試しに、ページ上に投稿を1つ表示してみましょう。表示制御のためdivタグで囲むことにします。最初に、Articleを1つ新規作成します。

Article.create(body: 'パーシャルレイアウトはいいぞ!')

showテンプレートは、boxレイアウトに内包された_articleパーシャルを出力します。

articles/show.html.erb

<%= render partial: 'article', layout: 'box', locals: { article: @article } %>

boxレイアウトは、divタグの中に_articleパーシャルを内包した簡単な構造です。

articles/_box.html.erb

<div class='box'>
  <%= yield %>
</div>

このパーシャルレイアウトでは、render呼び出しに渡されたローカルのarticle変数にアクセスできる点にご注目ください。ただし、アプリケーション全体で共通のレイアウトとは異なり、パーシャルレイアウトのファイル名冒頭にはアンダースコアが必要です。

yieldを呼び出す代わりに、パーシャルレイアウト内にあるコードのブロックをレンダリングすることも可能です。たとえば、_articleというパーシャルがない場合でも、以下のような呼び出しが行えます。

articles/show.html.erb

<% render(layout: 'box', locals: { article: @article }) do %>
  <div>
    <p><%= article.body %></p>
  </div>
<% end %>

ここでは、同じ_boxパーシャルを使う前提であり、先の例と同じ出力が得られます。

6 ビューのパス

レスポンスをレンダリングする場合、個別のビューが置かれている場所をコントローラが解決する必要があります。デフォルトでは、app/viewsディレクトリの下のみを探索します。

prepend_view_pathメソッドやappend_view_pathメソッドを用いることで、パスの解決時に優先して検索される別のディレクトリを追加できます。

6.1 ビューパスの冒頭にパスを追加する

これは、たとえばサブドメインで使うビューを別のディレクトリ内に配置したい場合などに便利です。

次のように利用できます。

prepend_view_path "app/views/#{request.subdomain}"

Action Viewは、ビューの解決時にこのディレクトリ内を最初に探索します。

6.2 ビューパスの末尾にパスを追加する

同様に、パスを末尾に追加することもできます。

append_view_path "app/views/direct"

上のコードは、探索パスの末尾にapp/views/directを追加します。

7 ヘルパー

Railsでは、Action Viewで利用できるヘルパーメソッドを多数提供しています。ヘルパーメソッドには以下のものが含まれます。

  • 日付・文字列・数値のフォーマット
  • 画像・動画・スタイルシートなどへのHTMLリンク作成
  • コンテンツのサニタイズ
  • フォームの作成
  • コンテンツのローカライズ

ヘルパーについて詳しくは、ガイドのAction View ヘルパーおよびAction View フォームヘルパーを参照してください。

8 ローカライズされたビュー

Action Viewは、現在のロケールに応じてさまざまなテンプレートをレンダリングできます。

たとえば、ArticlesControllerにshowアクションがあるとしましょう。このshowアクションを呼び出すと、デフォルトではapp/views/articles/show.html.erbが出力されます。ここでI18n.locale = :deを設定すると、代わりにapp/views/articles/show.de.html.erbがレンダリングされます。ローカライズ版のテンプレートが見当たらない場合は、装飾なしのバージョンが使われます。つまり、ローカライズ版ビューがなくても動作しますが、ローカライズ版ビューがあればそれが使われます。

同じ要領で、publicディレクトリのレスキューファイル (いわゆるエラーページ) もローカライズできます。たとえば、I18n.locale = :deと設定し、public/500.de.htmlpublic/404.de.htmlを作成することで、ローカライズ版のレスキューページを作成できます。

RailsはI18n.localeに設定できるシンボルを制限していないので、ローカライズにかぎらず、あらゆる状況に合わせて異なるコンテンツを表示し分けることが可能です。たとえば、エキスパートユーザーには通常ユーザーと異なる画面を表示したいとします。これを行なうには、app/controllers/application_controller.rbに以下のように追記します。

before_action :set_expert_locale

def set_expert_locale
  I18n.locale = :expert if current_user.expert?
end

これにより、たとえばapp/views/articles/show.expert.html.erbのような特殊なビューをエキスパートユーザーにだけ表示できます。

詳しくはRails 国際化 (i18n) API を参照してください。

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加