From 5bc793d3cf4212ddc3b6b51e63d117e15bc423c8 Mon Sep 17 00:00:00 2001 From: hachi8833 Date: Sun, 26 May 2024 16:59:05 +0900 Subject: [PATCH 1/4] =?UTF-8?q?action=5Fview=5Foverview.md=E3=81=AE?= =?UTF-8?q?=E8=A8=B3=E6=96=87=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- guides/source/ja/action_view_overview.md | 571 ++++++++++++++--------- 1 file changed, 344 insertions(+), 227 deletions(-) diff --git a/guides/source/ja/action_view_overview.md b/guides/source/ja/action_view_overview.md index 73ac60b790..0acac484ee 100644 --- a/guides/source/ja/action_view_overview.md +++ b/guides/source/ja/action_view_overview.md @@ -14,18 +14,24 @@ Action View の概要 Action Viewについて -------------------- -RailsにおけるWebリクエストは、[Action Controller](action_controller_overview.html)とAction Viewで扱われます。通常、Action Controllerは、データベースとのやりとりや、必要に応じたCRUD(Create/Read/Update/Delete)アクションの実行に関与します。Action View はその後レスポンスを実際のWebページにまとめる役割を担います。 +Action Viewは、[MVC][]のVに相当し、 [Action Controller](action_controller_overview.html)と連携してWebリクエストを処理します。Action Controllerは(MVCにおける)モデル層とのやりとりやデータの取得を担当し、次にAction Viewがそのデータを利用してWebリクエストに対するレスポンスのbody(本文)をレンダリングします。 -Action Viewのテンプレートは、HTMLタグの間にERB(Embedded Ruby)を含む形式で書かれます。ビューテンプレートがコードの繰り返しでうずまって乱雑になるのを避けるために、フォーム・日付・文字列に対して共通の動作を提供するヘルパークラスが多数用意されています。アプリケーションの機能向上に応じて独自のヘルパーを追加することも簡単にできます。 +デフォルトのAction Viewテンプレート(単に「ビュー」とも呼ばれます)は、HTMLドキュメント内にRubyコードを記述可能にするERB(Embedded Ruby)で記述します。 -NOTE: Action Viewの一部の機能はActive Recordと結びついていますが、Action ViewがActive Recordに依存しているわけではありません。Action Viewは独立したパッケージであり、任意のRubyライブラリと組み合わせて利用できます。 +Action Viewには、「フォーム」「日付」「文字列」用のHTMLタグを動的に生成する[ヘルパー](#helpers)メソッドが多数用意されています。必要であれば、アプリケーションに独自のヘルパーを追加することも可能です。 + +NOTE: Action Viewでは、コードを簡潔に書けるようにするためにActive Modelの[`to_param`][]メソッドや[`to_partial_path`][]メソッドを利用しています。ただし、Action ViewがActive Recordに依存しているわけではありません。Action Viewは独立したパッケージであり、任意のRubyライブラリと組み合わせて利用できます。 + +[MVC]: https://ja.wikipedia.org/wiki/Model_View_Controller +[`to_param`]: https://api.rubyonrails.org/classes/ActiveModel/Conversion.html#method-i-to_param +[`to_partial_path`]: https://api.rubyonrails.org/classes/ActiveModel/Conversion.html#method-i-to_partial_path Action ViewをRailsで使う ---------------------------- -アプリケーションの`app/views`ディレクトリには、1つのコントローラごとに1つのディレクトリが作成され、そこにビューテンプレートファイルが置かれます。このビューテンプレートはそのコントローラと関連付けられています。これらのファイルは、コントローラ内にあるアクションごとにレンダリング(画面への出力)された結果をビューで表示するために使われます。 +Action Viewテンプレート(別名「ビュー」)は、`app/views`ディレクトリ内のサブディレクトリに保存されます。ここには、コントローラーごとに、コントローラと同じ名前のサブディレクトリがあります。そのサブディレクトリ内にビューファイルが置かれ、コントローラーのアクションへのレスポンスとして特定のビューをレンダリングするします。 -scaffoldでリソースを生成するときに、Railsがデフォルトでどんなことを行なうのか見てみましょう。 +たとえば、scaffoldで`article`リソースを生成すると、`app/views/articles`ディレクトリに以下のファイルが生成されます。 ```bash $ bin/rails generate scaffold article @@ -42,20 +48,25 @@ $ bin/rails generate scaffold article [...] ``` -Railsのビューには命名規則があります。上で生成されたファイルを見るとわかるように、ビューテンプレートファイルは基本的にコントローラのアクションと関連付けられています。 -たとえば、`articles_controller.rb`コントローラのindexアクションは、`app/views/articles`ディレクトリの`index.html.erb`を使います。 +生成されるファイル名はRailsの命名規約に沿って、対応するコントローラーのアクション名がビューファイル名に取り入れられます(`index`アクションに対応する`index.html.erb`や、`edit`アクションに対応する`edit.html.erb`など)。 -これらのERBファイルに、それらをラップするレイアウトテンプレートや、ビューから参照されるあらゆるパーシャル(部分テンプレート)を組み合わせることで完全なHTMLが生成され、クライアントに送信されます。この後、本ガイドではこれらの3つの要素について詳しく説明します。 +この命名規約が守られていれば、Railsがコントローラーのアクションを実行し終わったときに、ユーザーが指定しなくても、そのアクションに対応するビューを自動的に探索してレンダリングします。たとえば、`articles_controller.rb`の`index`アクションを実行すると、`app/views/articles/`ディレクトリ内の`index.html.erb`ビューを自動的にレンダリングします。そのためには、ファイル名と置き場所の両方が規約に沿っていることが重要です。 -前述のとおり、Railsがレンダリングする最終的なHTMLは「テンプレート」「パーシャル」「レイアウト」の3つの要素から構成されます。 -これらについて簡単に説明します。 +クライアント(ブラウザ)に返される最終的なHTMLは、「ERB」ファイル(拡張子は`.html.erb`)、それをラップする「レイアウトテンプレート」、ERBファイルが参照するすべての「パーシャル」ファイル(部分テンプレートとも)の組み合わせで構成されます。本ガイドでは、この後「テンプレート」、「パーシャル」、「レイアウト」という3つのコンポーネントについてそれぞれ詳しく説明します。 テンプレート --------- -Action Viewのテンプレートはさまざまな方法で記述できます。テンプレートの拡張子が`.erb`であれば、ERB(Rubyのコードはここに含まれます)とHTMLを記述します。テンプレートの拡張子が`.builder`であれば、`Builder::XmlMarkup`ライブラリが使われます。 +Action Viewテンプレートは、さまざまなフォーマットで記述できます。 +テンプレートファイルの拡張子が`.erb`の場合は、HTMLレスポンスのビルドにERBが使われます。 +テンプレートファイルの拡張子が`.jbuilder`の場合は、JSONレスポンスのビルドに[Jbuilder][] gemが使われます。 +テンプレートファイルの拡張子が`.builder`の場合は、XMLレスポンスのビルドに[`Builder::XmlMarkup`][]ライブラリが使われます。 + +Railsは、複数のテンプレートシステムをファイル拡張子で区別します。 +たとえば、ERBテンプレートシステムを用いるHTMLファイルのファイル拡張子は`.html.erb`、Jbuilderテンプレートシステムを用いるJSONファイルのファイル拡張子は`.json.jbuilder`になります。他のテンプレートライブラリを利用すると、これ以外のテンプレート種別やファイル拡張子も追加される場合があります。 -Railsでは複数のテンプレートシステムがサポートされており、テンプレートファイルの拡張子で区別されます。たとえば、ERBテンプレートシステムを使うHTMLファイルの拡張子は`.html.erb`になります。 +[Jbuilder]: https://github.com/rails/jbuilder +[`Builder::XmlMarkup`]: https://github.com/rails/builder ### ERB @@ -65,27 +76,71 @@ ERBテンプレートの内部では、`<% %>`タグや`<%= %>`タグの中にRu 以下は、名前をレンダリングするためのループです。 +ERBテンプレートは、`<% %>`や`<%= %>`などの特殊なERBタグを利用して、Rubyコードを静的HTML内に埋め込む形で記述する方法です。 + +拡張子が`.html.erb`であるERBビューテンプレートをRailsが処理すると、ERB内のRubyコードが評価され、ERBタグを動的な出力に置き換えます。生成された動的コンテンツは静的なHTMLマークアップと結合されて、最終的なHTMLレスポンスが完成します。 + +ERBテンプレート内には、`<% %>`タグや`<%= %>`タグでRubyコードを記述できます。 +`<% %>`タグ(`=`を含まない)は、実行結果を出力せずにRubyコードを実行したい場合に使います(条件やループなど)。 +`<%= %>`タグ(`=`を含む)は、Rubyコードの実行結果を出力してテンプレート内でレンダリングしたい場合に使います(以下のコード例の`person.name`モデル属性など)。 + ```html+erb -

Names of all the people

+

Names

<% @people.each do |person| %> Name: <%= person.name %>
<% end %> ``` -ループの開始行と終了行は通常のERBタグ(`<% %>`)に書かれており、名前をレンダリングする行はレンダリング用のERBタグ(`<%= %>`)に書かれています。上のコードは、単にERBの書き方を説明しているだけではありません。Rubyでよく使われる`print`や`puts`のような通常のレンダリング関数はERBでは利用できませんのでご注意ください。以下のコードは誤りです。 +ループの開始行と終了行は通常のERBタグ(`<% %>`)に書かれており、名前をレンダリングする行はレンダリング用のERBタグ(`<%= %>`)に書かれています。 + +上のコードは、単にERBの書き方を説明しているだけではありません。Rubyでよく使われる`print`や`puts`のような通常のレンダリング関数は、ERBでは利用できませんのでご注意ください。たとえば以下のコードを書いても、ブラウザに`"Frodo"`は表示されません。 ```html+erb -<%# 誤り %> +<%# 以下のコードは無効 %> Hi, Mr. <% puts "Frodo" %> ``` -なお、Webページへのレンダリング結果の冒頭と末尾からホワイトスペースを取り除きたい場合は、`<%-` および `-%>`を通常の`<%` および `%>`と使い分けてください(訳注: これは英語のようなスペース分かち書きを行なう言語向けのノウハウです)。 +なお、ERBには上のように`<%# %>`でコメントを書くことも可能です。 + +Webページへのレンダリング結果の冒頭と末尾からホワイトスペースを取り除きたい場合は、通常の`<% %>`の代わりに`<%- -%>`を利用できます(訳注: これは英語のようなスペース分かち書きを行なう言語向けのノウハウです)。 + +### Jbuilder + +[Jbuilder](https://github.com/rails/jbuilder)はRailsチームによってメンテナンスされているgemの1つで、RailsのGemfileにデフォルトで含まれています。JbuilderはJSONレスポンスをビューテンプレートで生成するのに使われます。 + +Jbuilderが導入されていない場合は、Gemfileに以下を追加できます。 + +```ruby +gem 'jbuilder' +``` + +拡張子が`.jbuilder`のテンプレートでは、`json`という名前のJbuilderオブジェクトが自動的に利用可能になります。 + +基本的な例を以下に示します。 + +```ruby +json.name("Alex") +json.email("alex@example.com") +``` + +上のコードから以下のJSONが生成されます。 + +```json +{ + "name": "Alex", + "email": "alex@example.com" +} +``` + +詳しいコード例については[Jbuilderドキュメント](https://github.com/rails/jbuilder#jbuilder)を参照してください。 ### Builder -BuilderテンプレートはERBの代わりに利用できる、よりプログラミング向きな記法です。これ特にXMLコンテンツを生成するときに便利です。テンプレートの拡張子を`.builder`にすると、`xml`という名前のXmlMarkupオブジェクトが自動で利用できるようになります。 +BuilderテンプレートはERBの代わりに利用できる、よりプログラミング向きな記法です。これは`JBuilder`に似ていますが、JSONではなくXMLを生成するのに使われます。 -基本的な例を以下にいくつか示します。 +拡張子が`.builder`のテンプレートでは、`xml`という名前の`XmlMarkup`オブジェクトが自動的に利用可能になります。 + +基本的な例を以下に示します。 ```ruby xml.em("emphasized") @@ -103,7 +158,7 @@ xml.target("name" => "compile", "option" => "fast") ``` -ブロックを伴うメソッドはすべて、ブロックの中にネストしたマークアップを含むXMLマークアップタグとして扱われます。以下の例で示します。 +ブロックを渡されたメソッドはすべて、ブロックの中にネストしたマークアップを含むXMLマークアップタグとして扱われます。以下の例で示します。 ```ruby xml.div { @@ -121,93 +176,42 @@ xml.div { ``` -以下はBasecampで実際に使われているRSS出力コードをそのまま引用したものです。 - -```ruby -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 -``` - -### Jbuilder - -[Jbuilder](https://github.com/rails/jbuilder)はRailsチームによってメンテナンスされているgemの1つで、RailsのGemfileにデフォルトで含まれています。 - -JbuilderはBuilderと似ていますが、XMLではなくJSONを生成するのに使われます。 - -Jbuilderが導入されていない場合は、Gemfileに以下を追加できます。 - -```ruby -gem 'jbuilder' -``` - -`.jbuilder`という拡張子を持つテンプレートでは、`json`という名前のJbuilderオブジェクトが自動的に利用できるようになります。 +詳しいコード例については[Builderドキュメント](https://github.com/rails/builder)を参照してください。 -基本的な例を以下に示します。 - -```ruby -json.name("Alex") -json.email("alex@example.com") -``` - -上のコードから以下のJSONが生成されます。 - -```json -{ - "name": "Alex", - "email": "alex@example.com" -} -``` - -この他のコード例や詳しい情報については[Jbuilder documentation](https://github.com/rails/jbuilder#jbuilder)を参照してください。 - -### テンプレートをキャッシュする +### テンプレートをコンパイルする Railsは、デフォルトでビューの各テンプレートをコンパイルしてレンダリング用メソッドにします。developmentモードの場合、ビューテンプレートが変更されるとファイルの更新日時で変更が検出され、再コンパイルされます。 +ページのさまざまな部分を個別にキャッシュしたりキャッシュを失効させたりする必要がある場合には、フラグメントキャッシュも利用できます。詳しくは[キャッシュガイド](caching_with_rails.html#fragment-caching)を参照してください。 + パーシャル -------- -パーシャル(部分テンプレート)は、レンダリング処理を扱いやすく分割する仕組みです。パーシャルを使うことで、ビュー内のコードをいくつものファイルに分割して書き出し、他のテンプレートでも使い回せるようになります。 +パーシャル(部分テンプレート)は、ビューテンプレートを再利用可能な小さい部品に分割する方法です。パーシャルを利用することで、メインテンプレートのコードの一部を別の小さなファイルに抽出し、そのファイルをメインテンプレートでレンダリングできます。メインテンプレートからパーシャルファイルにデータを渡すことも可能です。 + +いくつかの例で実際の動作を見てみましょう。 ### パーシャルをレンダリングする -パーシャルをビューの一部に含めてレンダリングするには、ビューで`render`メソッドを使います。 +パーシャルをビューの一部に含めてレンダリングするには、ビューで以下のように[`render`][]メソッドを使います。 ```erb -<%= render "menu" %> +<%= render "product" %> ``` -上の呼び出しにより、`_menu.html.erb`という名前のファイルの内容が、`render`メソッドを書いたその場所でレンダリングされます。パーシャルファイル名の冒頭にアンダースコア(`_`)が付いていることにご注目ください。これは通常のビューと区別するために付けられています。 - -ただし`render`で呼び出す際にはこのアンダースコアは不要です。以下のように、他のフォルダの下にあるパーシャルを呼び出す際にもアンダースコアは不要です。 +上の呼び出しによって、`_product.html.erb`という名前のファイルが同じフォルダ内で検索され、そのビュー内でレンダリングされます。パーシャルファイル名の冒頭は、規約によりアンダースコア`_`で始まるので、パーシャルビューと通常のビューはファイル名で区別できます。ただし、レンダリングするパーシャルをビュー内で参照するときは、パーシャル名にアンダースコア`_`を追加せずに参照することにご注意ください。これは、以下のように別のディレクトリにあるパーシャルを参照する場合も同様です。 ```erb -<%= render "application/menu" %> +<%= render "application/product" %> ``` 上のコードは、その位置に`app/views/application/_menu.html.erb`パーシャルを読み込みます。 +[`render`]: https://api.rubyonrails.org/classes/ActionView/Helpers/RenderingHelper.html#method-i-render + ### パーシャルを活用してビューを簡潔に保つ -すぐに思い付くパーシャルの利用法といえば、パーシャルをサブルーチンと同等とみなすというのがあります。ビューの詳細部分をパーシャルに移動し、コードの見通しを良くするために、パーシャルを使うのです。たとえば、以下のようなビューがあるとします。 +すぐに思い付くパーシャルの利用法のひとつが、パーシャルをサブルーチンと同等とみなすという方法です。ビューの詳細部分をパーシャルに移動し、コードの見通しを良くするためにパーシャルを使うのです。たとえば、以下のようなビューがあるとします。 ```html+erb <%= render "application/ad_banner" %> @@ -222,132 +226,85 @@ Railsは、デフォルトでビューの各テンプレートをコンパイル <%= render "application/footer" %> ``` -上のコードの`_ad_banner.html.erb`パーシャルと`_footer.html.erb`パーシャルに含まれるコンテンツは、アプリケーションの多くのページと共有できます。あるページを開発中、パーシャルの部分については詳細を気にせずに済みます。 - -TIP: ビューのパーシャルは、テンプレートやレイアウトと同じ[テンプレートの継承](/layouts_and_rendering.html#テンプレートの継承)に依存しているので、`ApplicationController`から継承したコントローラでレンダリングするテンプレートは、`app/views/application`で宣言されたビューパーシャルを表示できるようになります。 +上のコード例にある`_ad_banner.html.erb`パーシャルと`_footer.html.erb`パーシャルに含まれるコンテンツは、アプリケーション内のさまざまなページと共有できます。この"Products"ページの開発中は、パーシャルの細かな表示内容を気にせずに済みます。 -コントローラでは、パーシャルを継承チェインで解決するだけでなく、デフォルトのパーシャルも継承チェインでオーバーライドできます。 -たとえば、`ProductsController`が`ApplicationController`を継承すると、`<%= render "ad_banner" %>`を呼び出したときに、最初に`app/views/products/_ad_banner.html.erb`を検索し、見つからない場合は`app/views/application/_ad_banner.html.erb`にフォールバックします。 +上のコード例では、`_product.html.erb`パーシャルも使われています。このパーシャルには、個別の製品をレンダリングするための詳細なコードが含まれていて、`@products`コレクション内にある個別の製品をリスト化してレンダリングするのに使われます。 -## `render`で`locals`オプションを渡す +## パーシャルに`locals`オプションでデータを渡す -パーシャルをレンダリングするときには、`locals:`オプションで渡された個別のキーを、そのパーシャル内のローカル変数として利用可能になります。 +あるビュー内でパーシャルをレンダリングするときに、パーシャルにデータを引数のように渡すことが可能です。パーシャルに渡すデータは、`locals:`オプションにハッシュの形で渡します。`locals:`オプションに渡したハッシュの各キーは、パーシャル内でローカル変数として参照可能になります。 ```html+erb <%# 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 %> -

<%= product.name %>

-<% end %> +<%= render partial: "product", locals: { my_product: @product } %> ``` -`locals:`オプションの一部としてビューに渡されていない変数をテンプレートが参照すると、テンプレートで`ActionView::Template::Error`エラーが発生します。 - ```html+erb <%# app/views/products/_product.html.erb %> -<%= tag.div id: dom_id(product) do %> -

<%= product.name %>

- - <%# => ActionView::Template::Errorが発生する %> - <% related_products.each do |related_product| %> - <%# ... %> - <% end %> +<%= tag.div id: dom_id(my_product) do %> +

<%= my_product.name %>

<% end %> ``` -### `local_assigns`を使う +「パーシャルローカル変数」とは、指定のパーシャル内のローカル変数、つまりそのパーシャル内でのみ利用可能な変数です。上のコード例では、`my_product`がパーシャルローカル変数であり、元のビューからパーシャルに渡されたときに、`@product`インスタンス変数の値が`my_product`に割り当てられたものです。 -`locals:`オプションに渡した個別のキーは、以下のように[`local_assigns`][]ヘルパーメソッドを使うことでパーシャルのローカル変数としてアクセスできるようになります。 +なお、このコード例では説明上インスタンス変数名やテンプレート名と一時的に区別するためにローカル変数名をあえて`my_product`としていますが、実際のコードでは`my_`などを付けずにインスタンス変数と一貫する`product`というローカル変数を使う方が一般的である点にご注意ください。 -```html+erb -<%# app/views/products/show.html.erb %> +`locals`はハッシュなので、必要に応じて`locals: { my_product: @product, my_reviews: @reviews }`のように複数の変数を渡すことも可能です。 -<%= render partial: "products/product", locals: { product: @product } %> -``` +ただし、`locals:`オプションの一部としてビューに渡していない変数がテンプレート内で参照されると、`ActionView::Template::Error`が発生します。 ```html+erb <%# app/views/products/_product.html.erb %> -<% local_assigns[:product] # => "#" %> -<% local_assigns[:options] # => nil %> -``` +<%= tag.div id: dom_id(my_product) do %> +

<%= my_product.name %>

-`local_assigns`は`Hash`なので、Ruby 3.1の[パターンマッチング代入演算子](https://docs.ruby-lang.org/en/master/syntax/pattern_matching_rdoc.html)と互換性があります。 - -```ruby -local_assigns => { product:, **options } -product # => "#" -options # => {} + <%# `product_reviews`は存在しないのでActionView::Template::Errorになる %> + <% product_reviews.each do |review| %> + <%# ... %> + <% end %> +<% end %> ``` -パーシャルの`Hash`ローカル変数に`:product`以外のキーを複数代入する場合、以下のようにsplat演算子`**`でヘルパーメソッド呼び出しに展開して渡せます。 +### `local_assigns`を使う -```html+erb -<%# app/views/products/_product.html.erb %> +個別のパーシャル内では[`local_assigns`][]というメソッドが利用可能です。このメソッドを用いると、`locals:`オプション経由で渡されたキーにアクセスできます。パーシャルがレンダリングされるときに`:some_key`が未設定の場合、パーシャル内の `local_assigns[:some_key]`の値は`nil`になります。 -<% local_assigns => { product:, **options } %> - -<%= tag.div id: dom_id(product), **options do %> -

<%= product.name %>

-<% end %> -``` +たとえば、以下のコード例の`product_reviews`は`nil`になります。これは、`locals:`に設定されているのが`product`だけであるためです。 ```html+erb <%# app/views/products/show.html.erb %> -<%= render "products/product", product: @product, class: "card" %> -<%# =>
- #

A widget

- #
-%> -``` +<%= render partial: "product", locals: { product: @product } %> -パターンマッチングの代入では、変数のリネームもサポートされます。 +<%# app/views/products/_product.html.erb %> -```ruby -local_assigns => { product: record } -product # => "#" -record # => "#" -product == record # => true +<% local_assigns[:product] # => "#" %> +<% local_assigns[:product_reviews] # => nil %> ``` -`local_assigns`は`Hash`のインスタンスを返すので、以下のように変数を条件付きで読み出して、`locals:`オプションにないキーの場合はデフォルト値にフォールバックできます。 +`local_assigns`のユースケースの1つは、ローカル変数をオプションとしてパーシャルに渡し、以下のようにローカル変数に値が設定済みかどうか基づいて条件付きで何らかの操作をパーシャル内で実行するというものです。 ```html+erb -<%# app/views/products/_product.html.erb %> - -<% local_assigns.fetch(:related_products, []).each do |related_product| %> - <%# ... %> +<% if local_assigns[:redirect] %> + <%= form.hidden_field :redirect, value: true %> <% end %> ``` -Ruby 3.1のパターンマッチング代入演算子に[`Hash#with_defaults`][]呼び出しを組み合わせると、パーシャルのローカル変数のデフォルト値代入を以下のようにコンパクトに書けます。 +別の例として、Active Storageの`_blob.html.erb`のコードを引用します。このコードは、この行を含むパーシャルをレンダリングするときに`in_gallery`ローカル変数が設定されているかどうかに基づいて表示サイズを設定します。 ```html+erb -<%# app/views/products/_product.html.erb %> - -<% local_assigns.with_defaults(related_products: []) => { product:, related_products: } %> - -<%= tag.div id: dom_id(product) do %> -

<%= product.name %>

- - <% related_products.each do |related_product| %> - <%# ... %> - <% end %> -<% end %> +<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> ``` [`local_assigns`]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns -[`Hash#with_defaults`]: https://api.rubyonrails.org/classes/Hash.html#method-i-with_defaults -### `partial`と`locals`オプションのない`render` +### `partial`や`locals`オプションを指定しない`render` -上の例では、`render`は`partial`と`locals`の2つのオプションを取っています。しかし、渡したいオプションが他にない場合は、これらのオプションを省略できます。 +上の例では、`render`に`partial`と`locals`という2つのオプションを渡しましたが、渡したいオプションが他にない場合は、これらのオプションのキー名を省略して値だけを渡すことも可能です。 次の例で説明します。 @@ -355,21 +312,29 @@ Ruby 3.1のパターンマッチング代入演算子に[`Hash#with_defaults`][] <%= render partial: "product", locals: { product: @product } %> ``` -上のコードは以下のようにも書けます。 +上のコードは以下のように値だけを渡す形でも書けます。 ```erb <%= render "product", product: @product %> ``` -### `as`と`object`オプション +上のコードは、パーシャル名とローカル変数名とインスタンス変数名が同じなので、以下のようにRailsの規約に沿った省略形でも書けます。 + +```erb +<%= render @product %> +``` + +この場合、`app/views/products/`ディレクトリ内で`_product.html.erb`というパーシャルを探索し、`product`というパーシャルローカル変数に`@product`インスタンス変数を設定します。 + +### `as`オプションと`object`オプション -`ActionView::Partials::PartialRenderer`は、デフォルトでテンプレートと同じ名前を持つローカル変数の中に自身のオブジェクトを持ちます。以下のコードを見てみましょう。 +`ActionView::Partials::PartialRenderer`は、デフォルトでテンプレートと同じ名前のローカル変数内に自身のオブジェクトを保持します。以下のコードを見てみましょう。 ```erb -<%= render partial: "product" %> +<%= render @product %> ``` -上のコードでは、`_product`パーシャルはローカル変数`product`から`@product`を取得できます。これは以下のコードと同等の結果になります。 +上のコードでは、`_product`パーシャル内でローカル変数`product`から`@product`を取得できます。これは以下のコードと同等の結果になります。 ```erb <%= render partial: "product", locals: { product: @product } %> @@ -383,13 +348,16 @@ Ruby 3.1のパターンマッチング代入演算子に[`Hash#with_defaults`][] <%= render partial: "product", locals: { product: @item } %> ``` -上のコードは以下のようになります。 +上のコードは以下のように書けます。 ```erb <%= render partial: "product", object: @item %> ``` -`as`オプションを使うと、ローカル変数に異なる名前を指定できます。たとえば、`product`ではなく`item`にしたい場合は次のようにします。 +ここでは、`@item`インスタンス変数が`product`という名前のパーシャルローカル変数に割り当てられます。 + +ローカル変数名をデフォルトの`product`から別の名前に変更したい場合は、`:as` オプションが使えます。 +`as`オプションを使うと、以下のようにローカル変数に別の名前を指定できます。 ```erb <%= render partial: "product", object: @item, as: "item" %> @@ -403,7 +371,7 @@ Ruby 3.1のパターンマッチング代入演算子に[`Hash#with_defaults`][] ### コレクションをレンダリングする -テンプレート上にコレクションを1つ表示し、サブテンプレートでそのコレクションの要素を1つずつレンダリングするというのは、よく行われるパターンです。このパターンは1つのメソッドだけで実行できます。このメソッドは配列を受け取り、配列内の各要素ごとにパーシャルを出力します。 +ビューで`@products`などのコレクションをイテレーションして、コレクション内のオブジェクトごとにパーシャルテンプレートをレンダリングするという方法は一般によく使われます。このパターンは、配列を受け取って、配列内の要素ごとにパーシャルをレンダリングする単一のメソッドとしてRailsに実装済みです。 すべての製品(products)を出力するコード例は以下のようになります。 @@ -419,15 +387,18 @@ Ruby 3.1のパターンマッチング代入演算子に[`Hash#with_defaults`][] <%= render partial: "product", collection: @products %> ``` -コレクションを渡してパーシャルが呼び出されると、パーシャルの各インスタンスは、パーシャル名に基づいた変数を経由してレンダリングされるコレクションのメンバーにアクセスします。このパーシャルは`_product`という名前なので、`product`を指定すれば、レンダリングされるインスタンスを取得できます。 +パーシャルにコレクションを渡して呼び出すと、パーシャルの個別のインスタンスは、そのパーシャル名に沿った変数を介して、レンダリングするコレクションのメンバーにアクセスできるようになります。この場合、パーシャルは`_product.html.erb`というファイルなので、レンダリングするコレクションのメンバーに`product`という名前のローカル変数名で参照できます。 -コレクションのレンダリングにはショートハンド記法があります。`@products`が`Product`インスタンスのコレクションであれば、以下のコードでも同じ結果を得られます。 +コレクションをレンダリングするために、Railsの規約に基づいた以下の省略形構文も利用できます。 ```erb <%= render @products %> ``` -使われるパーシャル名は、コレクションの中にある「モデル名」を参照して決定されます。この場合のモデル名は`Product`です。作成するコレクションの各要素が不揃い(訳注: 要素ごとにモデルが異なる場合を指します)であっても、Railsはコレクションのメンバごとに適切なパーシャルを選んでレンダリングします。 +上のコード例では、`@products`インスタンス変数が`Product`インスタンスのコレクションであることが前提です。 +Railsはコレクション内のモデル名(この場合は`Product`)を命名規約に沿って調べることで、利用するパーシャル名を決定します。 + +実は、この省略表現を使うと、コレクションがさまざまなモデルのインスタンスで構成されていてもレンダリング可能になります。Rails は、コレクションの各メンバーに適したパーシャルを選択します。 ### スペーサーテンプレート @@ -437,40 +408,217 @@ Ruby 3.1のパターンマッチング代入演算子に[`Hash#with_defaults`][] <%= render partial: @products, spacer_template: "product_ruler" %> ``` -メインの`_product`パーシャルの間に、スペーサーとなる`_product_ruler`パーシャルをレンダリングします(`_product_ruler`にはデータを渡していません)。 +上のコードは、メインの`_product`パーシャル同士の空きを調整するスペーサーとなる`_product_ruler`パーシャルをレンダリングします(`_product_ruler`にはデータを渡していません)。 + +### カウンタ変数 + +Railsでは、コレクションによって呼び出されるパーシャル内でカウンタ変数も利用可能です。カウンタ変数名は、パーシャル名の後ろに`_counter`を追加したものになります。たとえば、`@products`コレクションをレンダリングする場合、`_product.html.erb`パーシャルで`product_counter`変数にアクセスできます。このカウンタ変数は、最初のレンダリング時点の値が`0`から始まり、パーシャルの外側のビュー内でパーシャルがレンダリングされた回数を参照します。 + +```erb +<%# index.html.erb %> +<%= render partial: "product", collection: @products %> +``` + +```erb +<%# _product.html.erb %> +<%= product_counter %> # 1個目のproductは0、2個目のproductは1... +``` + +カウンタ変数は、`as:`オプションで名前が変更されたローカル変数でも利用できます。つまり、`as: :item`を指定すると、カウンタ変数は`item_counter`になります。 + +NOTE: 後述する2つのセクション[厳密な`locals`](#厳密なlocals)と[`local_assigns`でパターンマッチングを活用する](#local_assignsでパターンマッチングを活用する)では、より高度なパーシャルの利用法が説明されていますが、完全を期すためにここでも記載します。 + +### `local_assigns`でパターンマッチングを活用する + +`local_assigns`は`Hash`なので、[Ruby 3.1のパターンマッチング代入演算子][pattern matching]と互換性があります。 + +```ruby +local_assigns => { product:, **options } +product # => "#" +options # => {} +``` + +パーシャルローカル変数に`:product`以外のキーを複数持つ`Hash`を代入する場合、以下のようにsplat演算子`**`でヘルパーメソッド呼び出しに展開して渡すことが可能です。 + +```html+erb +<%# app/views/products/_product.html.erb %> + +<% local_assigns => { product:, **options } %> + +<%= tag.div id: dom_id(product), **options do %> +

<%= product.name %>

+<% end %> +``` + +```html+erb +<%# app/views/products/show.html.erb %> + +<%= render "products/product", product: @product, class: "card" %> +<%# =>
+ #

A widget

+ #
+%> +``` + +パターンマッチングの代入では、変数のリネームもサポートされます。 + +```ruby +local_assigns => { product: record } +product # => "#" +record # => "#" +product == record # => true +``` + +`local_assigns`で`fetch`を使うと、以下のように変数を条件付きで読み取り、キーが`locals:`オプションに含まれていない場合にデフォルト値にフォールバックすることも可能です。 + +```html+erb +<%# app/views/products/_product.html.erb %> + +<% local_assigns.fetch(:related_products, []).each do |related_product| %> + <%# ... %> +<% end %> +``` + +Ruby 3.1のパターンマッチング代入演算子に[`Hash#with_defaults`][]呼び出しを組み合わせると、パーシャルローカル変数のデフォルト値代入を以下のようにコンパクトに書けます。 + +```html+erb +<%# app/views/products/_product.html.erb %> + +<% local_assigns.with_defaults(related_products: []) => { product:, related_products: } %> + +<%= tag.div id: dom_id(product) do %> +

<%= product.name %>

+ + <% related_products.each do |related_product| %> + <%# ... %> + <% end %> +<% end %> +``` + +INFO: デフォルトのパーシャルは、`locals`として任意のキーワード引数を受け取れます。パーシャルが受け取れる`locals`のキーワード引数を限定するには、`locals:`マジックコメントを使います。詳しくは、次の[厳密な`locals`](#厳密なlocals)を参照してください。 + +[pattern matching]: https://docs.ruby-lang.org/en/master/syntax/pattern_matching_rdoc.html +[`local_assigns`]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns +[`Hash#with_defaults`]: https://api.rubyonrails.org/classes/Hash.html#method-i-with_defaults ### 厳密な`locals` -デフォルトでは、テンプレートはキーワード引数として任意の`locals`を受け取れます。テンプレートが受け取ってよい`locals`を定義するには、以下のように`locals`マジックコメントを追加します。 +デフォルトのテンプレートは、`locals`として受け取れるキーワード引数の個数に制約がありません。テンプレートが受け取ってよい`locals`のキーワード引数やその個数に制約をかけたりデフォルト値を設定したりするには、以下のように`locals`マジックコメントをビューに追加します。 + +`locals:`マジックコメントの例を以下に示します。 ```erb +<%# app/views/messages/_message.html.erb %> + <%# locals: (message:) -%> <%= message %> ``` -`locals`にはデフォルト値も設定できます。 +上のコード例では、`message`ローカル変数が必須になり、パーシャルを呼び出すときに省略できなくなります。引数に`:message`ローカル変数を渡さずにこのパーシャルをレンダリングすると例外が発生します。 + +```ruby +render "messages/message" +# => ActionView::Template::Error: missing local: :message for app/views/messages/_message.html.erb +``` + +以下のように`message`にデフォルト値を設定しておくと、`message`が渡されない場合にそのデフォルト値が使われます。 ```erb +<%# app/views/messages/_message.html.erb %> + <%# locals: (message: "Hello, world!") -%> <%= message %> ``` -以下のように、`locals`を完全に無効にすることも可能です。 +上のパーシャルに`:message`ローカル変数を渡さずにレンダリングすると、`locals:`マジックコメントで設定したデフォルト値が使われます。 + +```ruby +render "messages/message" +# => "Hello, world!" +``` + +同様に、`local:`マジックコメントで許可されていないローカル変数を渡してパーシャルをレンダリングすると、例外が発生します。 + +```ruby +render "messages/message", unknown_local: "will raise" +# => ActionView::Template::Error: unknown local: :unknown_local for app/views/messages/_message.html.erb +``` + +以下のようにdouble splat演算子`**`も併用すると、オプションのローカル変数も引数として渡せるようになります。 + +```erb + +<%# app/views/messages/_message.html.erb %> + +<%# locals: (message: "Hello, world!", **attributes) -%> +<%= tag.p(message, **attributes) %> +``` + +逆に、以下のように`locals: ()`を設定すると、`locals`を完全に無効にできます。 ```erb +<%# app/views/messages/_message.html.erb %> + <%# locals: () %> ``` +上のコード例では、パーシャルにどんなローカル変数を渡しても以下のように例外が発生します。 + +```ruby +render "messages/message", unknown_local: "will raise" +# => ActionView::Template::Error: no locals accepted for app/views/messages/_message.html.erb +``` + +Action Viewでは、`#`で始まるコメントをサポートする任意のテンプレートエンジンで`locals:`マジックコメントを処理します。また、マジックコメントはパーシャル内のどの行に書かれていても認識されます。 + +CAUTION: サポートされているのはキーワード引数のみです。位置引数やブロック引数が使われると、レンダリング時にAction Viewエラーが発生します。 + ### レイアウト -Railsにおける「レイアウト」は、多くのコントローラのアクションにわたって共通して利用できるテンプレートのことです。Railsアプリケーションには必ず全体用のレイアウトがあり、ほぼすべてのWebページ出力はこの全体レイアウトの内側で行われますが、これが典型的なレイアウトです。 +レイアウト(layout)を使うと、Railsのさまざまなコントローラアクションの結果を共通のビューテンプレートでレンダリングできます。Railsアプリケーションでは、ページのレンダリングに複数のレイアウトを利用可能です。 + +たとえば、あるアプリケーションでは、ユーザーログインページでログインに適したレイアウトを利用し、マーケティングやセールス用ページではそれに適した別のレイアウトを利用できます。ログインしたユーザー向けのレイアウトであれば、ナビゲーションツールバーをページのトップレベルに表示するスタイルを多くのコントローラやアクションで共通化することも可能です。SaaSアプリケーションの商品販売用レイアウトであれば、トップレベルのナビゲーションに「お値段」や「お問い合わせ先」を共通して表示できます。また、レイアウトごとにヘッダーやフッターのコンテンツを変更することも可能です。 + +Railsでは、現在のコントローラアクションに対応するレイアウトを探索するために、最初にコントローラと同じベース名を持つファイルを`app/views/layouts`ディレクトリ内で探します。たとえば、`ProductsController`クラスのアクションをレンダリングする場合、`app/views/layouts/products.html.erb`が使われます。 + +コントローラに対応するレイアウトが存在しない場合は、`app/views/layouts/application.html.erb` + +以下は、`application.html.erb`ファイルのシンプルなレイアウトの例です。 + +```html+erb + + + + <%= "Your Rails App" %> + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + + +<%= yield %> + +
+

© <%= Date.current.year %> Your Company

+
+``` + +上のレイアウト例では、`<%= yield %>`の部分でビューコンテンツがレンダリングされ、それが``や`