本ガイドでは、Action ControllerとAction Viewによる基本的なレイアウト機能について解説します。
このガイドの内容:
本ガイドでは、コントローラ、ビュー、モデルによって形成される三角形のうち、コントローラとビューの間でのやりとりを中心に扱います。ご存じのように、Railsのコントローラはリクエストを扱うプロセス全体の流れを組織的に調整する責任を負い、(ビジネスロジックのような) 重い処理はモデルの方で行なうのが普通です。モデル側での処理が完了し、ユーザーに結果を表示する時がきたら、コントローラは処理結果をビューに渡します。このときの、コントローラからビューへの結果の渡し方こそが本ガイドの主なトピックです。
大きな流れとしては、ユーザーへのレスポンスとして送信すべき内容を決定することと、ユーザーへのレスポンスを作成するために適切なメソッドを呼び出すこともこの作業に含まれます。ユーザーに返すレスポンス画面を完全なビューにするのであれば、Railsはそのビューをさらに別のレイアウトでラッピングし、パーシャルビューとして取り出すでしょう。以後本ガイドではこれらの方法をすべて紹介します(訳注: 本ガイドではrenderを一般的な意味では「出力」、具体的な動作を指す場合は「レンダリング」と訳しています)。
コントローラ側から見ると、HTTPレスポンスの作成方法は以下の3とおりあります。
render
を呼び出し、ブラウザに返す完全なレスポンスを作成するredirect_to
を呼び出し、HTTPリダイレクトコードステータスをブラウザに送信するhead
を呼び出し、HTTPヘッダーのみで構成されたレスポンスを作成してブラウザに送信するRailsでは「設定より規約 (CoC: convention over configuration)」というポリシーが推奨されていることをご存じかと思います。デフォルトの出力結果は、CoCのよい例でもあります。Railsのコントローラは、デフォルトでは正しいルーティングに対応する名前を持つビューを自動的に選び、それを使用してレスポンスを出力します。たとえば、BooksController
というコントローラに以下のコードがあるとします。
class BooksController < ApplicationController end
ルーティングファイルに以下が記載されているとします。
resources :books
app/views/books/index.html.erb
ビューファイルの内容が以下のようになっているとします。
<h1>Books are coming soon!</h1>
以上のようにすることで、ユーザーがブラウザで/books
にアクセスすると、Railsは自動的にapp/views/books/index.html.erb
ビューを使用してレスポンスを出力し、その結果「Books are coming soon!」という文字が画面に表示されます。
しかしこの画面だけではほとんど実用性がないので、Book
モデルを作成し、BooksController
にindexアクションを追加してみましょう。
class BooksController < ApplicationController def index @books = Book.all end end
上のコードでご注目いただきたいのは、「設定より規約」の原則が利いているおかげでindexアクションの最後で明示的に画面出力を指示する必要がないという点です。ここでの原則は、「コントローラのアクションの最終部分で明示的な画面出力が指示されていない場合は、コントローラが使用できるビューのパスからアクション名.html.erb
というビューテンプレートを探し、それを使用して自動的に出力する」というものです。従って、この場合はapp/views/books/index.html.erb
ファイルが出力されます。
ビューですべての本の属性を表示したい場合は、以下のようにERBを書くことができます。
<h1>Listing Books</h1> <table> <thead> <tr> <th>Title</th> <th>Content</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @books.each do |book| %> <tr> <td><%= book.title %></td> <td><%= book.content %></td> <td><%= link_to "Show", book %></td> <td><%= link_to "Edit", edit_book_path(book) %></td> <td><%= link_to "Destroy", book, method: :delete, data: { confirm: "Are you sure?" } %></td> </tr> <% end %> </tbody> </table> <br> <%= link_to "New book", new_book_path %>
実際のレンダリングは、ActionView::TemplateHandlers
のサブクラスで行われます。本ガイドではレンダリングの詳細については触れませんが、テンプレートハンドラの選択がビューテンプレートファイルの拡張子によって制御されているという重要な点は理解しておいてください。Rails 2以降におけるビューテンプレートの標準拡張子は、ERB (HTML + eMbedded RuBy) でレンダリングする場合は.erb
、Builder (XMLジェネレータ) でレンダリングする場合は.builder
です。
render
を使用するアプリケーションがブラウザで表示するコンテンツのレンダリング (出力) という力仕事は、ActionController::Base#render
メソッドがほぼ一手に引き受けています。render
メソッドはさまざまな方法でカスタマイズできます。Railsテンプレートのデフォルトビューを出力することもできますし、特定のテンプレート、ファイル、インラインコードを指定して出力したり、何も出力しないこともできます。テキスト、JSON、XMLを出力することもできます。出力されるレスポンスのcontent typeやHTTPステータスを指定することもできます。
出力結果をブラウザで表示して調べることなく、render
呼び出しの正確な結果を取得したい場合は、render_to_string
を呼び出すことができます。このメソッドの動作はrender
と完全に同じであり、出力結果をブラウザに返さずに文字列を返す点だけが異なります。
同じコントローラで、デフォルトと異なるテンプレートに対応するビューを出力したい場合は、render
メソッドでビュー名を指定することができます。
def update @book = Book.find(params[:id]) if @book.update(book_params) redirect_to(@book) else render "edit" end end
上のupdate
アクションでモデルに対するupdate
メソッドの呼び出しが失敗すると、同じコントローラに用意しておいた別のedit.html.erb
テンプレートを使用して出力します。
出力するアクションを指定するには、文字列の他にシンボルを使用することもできます。
def update @book = Book.find(params[:id]) if @book.update(book_params) redirect_to(@book) else render :edit end end
あるコントローラのアクションから、まったく別のコントローラの配下にあるテンプレートを使用して出力することは可能でしょうか。これもrender
メソッドだけで行なうことができます。render
メソッドにはapp/views
を起点とするフルパスを渡すことができますので、出力したいテンプレートをフルパスで指定します。たとえば、app/controllers/admin
に置かれているAdminProducts
コントローラのコードを実行しているとすると、app/views/products
に置かれているビューテンプレートに対するアクションの実行結果を出力するには以下のようにします。
render "products/show"
パスにスラッシュ/
が含まれていると、Railsによってこのビューは異なるコントローラの配下にあると認識されます。異なるコントローラのテンプレートを指定していることをより明示的にしたい場合は、以下のように:template
オプションを使用することもできます (Rails 2.2以前ではこのオプションは必須でした)。
render template: "products/show"
render
メソッドで指定するビューは、現在のアプリケーションディレクトリの外部にあっても構いません (2つのRailsアプリケーションでビューを共有しているなどの場合)。
render "/u/apps/warehouse_app/current/app/views/products/show"
パスがスラッシュ/
で始まっている場合、Railsはこのコードがファイルの出力であると認識します。ファイルを出力することをより明示的にしたい場合は、以下のように:file
オプションを使用することもできます (Rails 2.2以前ではこのオプションは必須でした)。
render file: "/u/apps/warehouse_app/current/app/views/products/show"
:file
オプションに与えるパスは、ファイルシステムの絶対パスです。当然ながら、コンテンツを出力したいファイルに対して適切なアクセス権が与えられている必要があります。
:file
オプションをユーザーの入力と組み合わせて使用すると、攻撃者がこのアクションを使用してファイルシステム内のセキュリティ上重要なファイルにアクセスする可能性があるため、セキュリティ上の問題が生じる可能性があります。
ファイルを出力する場合、デフォルトでは現在のレイアウトを使用してレンダリングされます。
Microsoft Windows上でRailsを実行している場合、ファイルを出力する際に:file
オプションを省略できません。Windowsのファイル名フォーマットはUnixのファイル名と同じではないためです。
これまでご紹介した3通りの出力方法 (コントローラ内の別テンプレートを使用、別のコントローラのテンプレートを使用、ファイルシステム上の任意のファイルを使用) は、実際には同一のアクションのバリエーションにすぎません。
実のところ、たとえばBooksControllerクラスのupdateアクション内で、本の更新に失敗したらeditテンプレートを出力したいとすると、以下のどのレンダリング呼び出しを行っても最終的には必ずviews/books
ディレクトリのedit.html.erb
を使用して出力が行われます。
render :edit render action: :edit render "edit" render "edit.html.erb" render action: "edit" render action: "edit.html.erb" render "books/edit" render "books/edit.html.erb" render template: "books/edit" render template: "books/edit.html.erb" render "/path/to/rails/app/views/books/edit" render "/path/to/rails/app/views/books/edit.html.erb" render file: "/path/to/rails/app/views/books/edit" render file: "/path/to/rails/app/views/books/edit.html.erb"
どの呼び出しを使用するかはコーディングのスタイルと規則の問題でしかありませんが、経験上なるべくシンプルな記法を使用する方がコードがわかりやすくなるでしょう。
render
で:inline
オプションを使用するrender
メソッドは、メソッド呼び出しの際に:inline
オプションを使用してERBを与えると、ビューがまったくない状態でも実行することができます。これは完全に有効な方法です。
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
このオプションを実際に使用する意味はほぼないと思われます。コントローラのコードにERBを混在させると、RailsのMVC指向が崩されるだけでなく、開発者がプロジェクトのロジックを追いかけることが困難になってしまいます。通常のERBビューを使用してください。
インラインでは、デフォルトでERBを使用して出力を行います。:type
オプションで:builderを指定すると、ERBに代えてBuilderが使用されます。
render inline: "xml.p {'Horrid coding practice!'}", type: :builder
render
で:plain
オプションを使用すると、平文テキストをマークアップせずにブラウザに送信することができます。
render plain: "OK"
平文テキストの出力は、AjaxやWebサービスリクエストに応答するときに最も有用です。これらではHTML以外の応答を期待しています。
デフォルトでは、:plain
オプションを使用すると出力結果に現在のレイアウトが適用されません。テキストの出力を現在のレイアウト内で行いたい場合は、layout: true
オプションを追加して.text.erb
を使う必要があります。
render
で:html
オプションを使用すると、HTML文字列を直接ブラウザに送信することができます。
render html: helpers.tag.strong('Not Found')
この手法は、HTMLコードのごく小規模なスニペットを出力したい場合に便利です。 スニペットのマークアップが複雑になるようであれば、早めにテンプレートファイルに移行することをご検討ください。
html:
オプションを使用すると、html_safe
を理解できるAPIで文字列が組み立てられていない場合にHTMLエンティティがエスケープされます。
JSONはJavaScriptのデータ形式の一種で、多くのAjaxライブラリで使用されています。Railsでは、オブジェクトからJSON形式への変換と、変換されたJSONをブラウザに送信する機能がビルトインでサポートされています。
render json: @product
出力するオブジェクトに対してto_json
を呼び出す必要はありません。:json
オプションが指定されていれば、render
によってto_json
が自動的に呼び出されるようになっています。
Railsでは、オブジェクトからXML形式への変換と、変換されたXMLをブラウザに送信する機能がビルトインでサポートされています。
render xml: @product
出力するオブジェクトに対してto_xml
を呼び出す必要はありません。:xml
オプションが指定されていれば、render
によってto_xml
が自動的に呼び出されるようになっています。
Railsはvanilla JavaScriptを出力することもできます。
render js: "alert('Hello Rails');"
上のコードは、引数で与えられた文字列をMIMEタイプtext/javascript
でブラウザに送信します。
render
で:body
オプションを指定することで、content typeを一切指定しない生のコンテンツをブラウザに送信することができます。
render body: "raw"
このオプションを使用するのは、レスポンスのcontent typeがどんなものであってもよい場合のみにしてください。ほとんどの場合、:plain
や:html
などを使用する方が適切です。
このオプションを使用してブラウザに送信されるレスポンスは、上書きされない限りtext/plain
が使用されます。これはAction Dispatchによるレスポンスのデフォルトのcontent typeであるためです。
render
のオプションrender
メソッドに対する呼び出しでは、一般に以下の5つのオプションが使用できます。
:content_type
:layout
:location
:status
:formats
:content_type
オプションRailsがデフォルトで出力する結果のMIME content-typeは、デフォルトでtext/html
になります (ただし:json
を指定した場合にはapplication/json
、:xml
を使用した場合はapplication/xml
になります)。content-typeを変更したい場合は、:content_type
オプションを指定します。
render file: filename, content_type: "application/rss"
:layout
オプションrender
で指定できるほとんどのオプションでは、出力されるコンテンツは現在のレイアウトの一部としてブラウザ上で表示されます。これより、レイアウトの詳細と利用法について本ガイドで説明します。
:layout
オプションを指定すると、現在のアクションに対して特定のファイルをレイアウトとして使用します。
render layout: "special_layout"
出力時にレイアウトをまったく使用しないよう指定することもできます。
render layout: false
:location
オプション:location
を使用することで、HTTPのLocation
ヘッダーを設定できます。
render xml: photo, location: photo_url(photo)
:status
オプションRailsが返すレスポンスのHTTPステータスコードは自動的に生成されます (ほとんどの場合200 OK
となります)。:status
オプションを使用することで、レスポンスのステータスコードを変更できます。
render status: 500 render status: :forbidden
ステータスコードは数字で指定する他に、以下に示すシンボルで指定することもできます。
レスポンスクラス | HTTPステータスコード | シンボル |
---|---|---|
Informational | 100 | :continue |
101 | :switching_protocols | |
102 | :processing | |
Success | 200 | :ok |
201 | :created | |
202 | :accepted | |
203 | :non_authoritative_information | |
204 | :no_content | |
205 | :reset_content | |
206 | :partial_content | |
207 | :multi_status | |
208 | :already_reported | |
226 | :im_used | |
Redirection | 300 | :multiple_choices |
301 | :moved_permanently | |
302 | :found | |
303 | :see_other | |
304 | :not_modified | |
305 | :use_proxy | |
307 | :temporary_redirect | |
308 | :permanent_redirect | |
Client Error | 400 | :bad_request |
401 | :unauthorized | |
402 | :payment_required | |
403 | :forbidden | |
404 | :not_found | |
405 | :method_not_allowed | |
406 | :not_acceptable | |
407 | :proxy_authentication_required | |
408 | :request_timeout | |
409 | :conflict | |
410 | :gone | |
411 | :length_required | |
412 | :precondition_failed | |
413 | :payload_too_large | |
414 | :uri_too_long | |
415 | :unsupported_media_type | |
416 | :range_not_satisfiable | |
417 | :expectation_failed | |
421 | :misdirected_request | |
422 | :unprocessable_entity | |
423 | :locked | |
424 | :failed_dependency | |
426 | :upgrade_required | |
428 | :precondition_required | |
429 | :too_many_requests | |
431 | :request_header_fields_too_large | |
451 | :unavailable_for_legal_reasons | |
Server Error | 500 | :internal_server_error |
501 | :not_implemented | |
502 | :bad_gateway | |
503 | :service_unavailable | |
504 | :gateway_timeout | |
505 | :http_version_not_supported | |
506 | :variant_also_negotiates | |
507 | :insufficient_storage | |
508 | :loop_detected | |
510 | :not_extended | |
511 | :network_authentication_required |
「non-content」ステータスコード (100-199、204、205、 304のいずれか)でレンダリングしようとすると、レスポンスから削除されます。
:formats
オプションRailsは、リクエストで指定されたフォーマットを使います(またはデフォルトのhtml
)。:formats
に渡すオプションは、シンボルや配列で変更できます。
render formats: :xml render formats: [:json, :xml]
指定されたフォーマットのテンプレートが存在しない場合は、ActionView::MissingTemplate
エラーになります。
Railsは現在のレイアウトを探索する場合、最初に現在のコントローラと同じ基本名を持つレイアウトがapp/views/layouts
ディレクトリにあるかどうかを調べます。たとえば、PhotosController
クラスのアクションから出力するのであれば、app/views/layouts/photos.html.erb
またはapp/views/layouts/photos.builder
を探します。該当のコントローラに属するレイアウトがない場合、app/views/layouts/application.html.erb
またはapp/views/layouts/application.builder
を使用します。.erb
レイアウトがない場合、.builder
レイアウトがあればそれを使用します。Railsには、各コントローラやアクションに割り当てる特定のレイアウトをもっと正確に指定する方法がいくつも用意されています。
layout
宣言を使用することで、デフォルトのレイアウト名ルールを上書きすることができます。例:
class ProductsController < ApplicationController layout "inventory" #... end
この宣言によって、ProductsController
からの出力で使用されるレイアウトはapp/views/layouts/inventory.html.erb
になります。
アプリケーション全体で特定のレイアウトを使用したい場合は、ApplicationController
クラスでlayout
を宣言します。
class ApplicationController < ActionController::Base layout "main" #... end
この宣言によって、アプリケーションのすべてのビューで使用されるレイアウトはapp/views/layouts/main.html.erb
になります。
レイアウトの指定にシンボルを使用することで、リクエストが実際に処理されるときまでレイアウトを確定せず、選択を遅延することができます。
class ProductsController < ApplicationController layout :products_layout def show @product = Product.find(params[:id]) end private def products_layout @current_user.special? ? "special" : "products" end end
上のコードは、現在のユーザーが特別なユーザーの場合、そのユーザーが製品ページを見るときに特別なレイアウトを適用します。
レイアウトを決定する際に、Procなどのインラインメソッドを使用することもできます。たとえばProcオブジェクトを渡すと、Procを渡されたブロックにはcontroller
インスタンスが渡されます。これにより、現在のリクエストを元にしてレイアウトを決定することができます。
class ProductsController < ApplicationController layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" } end
コントローラレベルで指定されたレイアウトでは、:only
オプションと:except
オプションがサポートされています。これらのオプションは、単一のメソッド名またはメソッド名の配列を引数として受け取ります。渡すメソッド名はコントローラ内のメソッド名に対応します。
class ProductsController < ApplicationController layout "product", except: [:index, :rss] end
上の宣言によって、rss
メソッドとindex
メソッド以外のすべてのメソッドにproduct
レイアウトが適用されます。
レイアウト宣言は下の階層に継承されます。下の階層、つまりより具体的なレイアウト宣言は、上の階層、つまりより一般的なレイアウトよりも常に優先されます。例:
application_controller.rb
class ApplicationController < ActionController::Base layout "main" end
posts_controller.rb
class PostsController < ApplicationController end
special_posts_controller.rb
class SpecialPostsController < PostsController layout "special" end
old_posts_controller.rb
class OldPostsController < SpecialPostsController layout false def show @post = Post.find(params[:id]) end def index @old_posts = Post.older render layout: "old" end # ... end
上のアプリケーションは以下のように動作します。
main
レイアウトが使用されます。PostsController#index
ではmain
レイアウトが使用されます。SpecialPostsController#index
ではspecial
レイアウトが使用されます。OldPostsController#show
ではレイアウトが適用されません。OldPostsController#index
ではold
レイアウトが使用されます。レイアウト継承のロジックと同様に、テンプレートやパーシャルが通常のパスで見つからない場合、コントローラーは継承パスを探索してレンダリングするテンプレートやパーシャルを見つけようとします。次の例をご覧ください。
# app/controllers/application_controller class ApplicationController < ActionController::Base end # app/controllers/admin_controller class AdminController < ApplicationController end # app/controllers/admin/products_controller class Admin::ProductsController < AdminController def index end end
このときのadmin/products#index
アクションの探索順序は次のようになります。
app/views/admin/products/
app/views/admin/
app/views/application/
つまり、app/views/application/
は共有パーシャルの置き場所として手頃です。これらはERBで次のようにレンダリングされます。
<%# app/views/admin/products/index.html.erb %> <%= render @products || "empty_list" %> <%# app/views/application/_empty_list.html.erb %> There are no items in this list <em>yet</em>.
Rails開発をやっていれば、一度は "Can only render or redirect once per action" エラーに遭遇したことがあるでしょう。いまいましいエラーですが、修正は比較的簡単です。このエラーはほとんどの場合、開発者がrender
メソッドの基本的な動作を誤って理解していることが原因です。
このエラーを発生する以下のコードを例にとって説明しましょう。
def show @book = Book.find(params[:id]) if @book.special? render action: "special_show" end render action: "regular_show" end
@book.special?
がtrue
の場合、Railsはレンダリングを開始し、@book
変数をspecial_show
ビューに転送します。しかし、show
アクションのコードはそこで 止まらない ことにご注意ください。show
アクションのコードは最終行まで実行され、regular_show
ビューのレンダリングを行おうとした時点でエラーが発生します。解決法はいたって単純です。1つのコード実行パス内では、render
メソッドやredirect
メソッドの実行は1度だけにしてください。ここで非常に便利なのがand return
というメソッドです。このメソッドを使用して修正したバージョンを以下に示します。
def show @book = Book.find(params[:id]) if @book.special? render action: "special_show" and return end render action: "regular_show" end
&& return
ではなくand return
を使用してください。&& return
はRuby言語の&&演算子の優先順位が高すぎてこの文脈では正常に動作しません。
RailsにビルトインされているActionControllerが行なう暗黙のレンダリングでは、render
メソッドが呼び出されたかどうかを確認してからレンダリングを開始します。従って、以下のコードは正常に動作します。
def show @book = Book.find(params[:id]) if @book.special? render action: "special_show" end end
上のコードは、ある本がspecial?
である場合にのみspecial_show
テンプレートを使用して出力します。それ以外の場合はshow
テンプレートを使用して出力します。
redirect_to
を使用するHTTPリクエストにレスポンスを返すもう一つの方法は、redirect_to
を使用することです。前述のとおり、render
はレスポンス構成時にどのビュー (または他のアセット) を使用するかを指定するためのものです。redirect_to
メソッドは、この点においてrender
メソッドと根本的に異なります。redirect_to
メソッドは、別のURLに対して改めてリクエストを再送信するよう、ブラウザに指令を出すためのものです。たとえば以下の呼び出しを行なうと、アプリケーションで現在どのページが表示されていても、写真のインデックス表示ページにリダイレクトされます。
redirect_to photos_url
redirect_back
を使うと、ユーザを直前のページに戻すことができます。戻る場所はHTTP_REFERER
ヘッダを利用していますが、これはブラウザが必ず設定しているとは限りません。そのため、fallback_location
は必ず設定しなければなりません。
redirect_back(fallback_location: root_path)
redirect_to
やredirect_back
はメソッドの実行を即座に中断したりはしません。これらは単にHTTPのレスポンスを設定するだけです。もしこれらの後にメソッドがあった場合そのメソッドは実行されてしまいます。必要であれば、明示的なreturn
もしくは他の中断用の手法を使うことで中断可能です。
redirect_to
を呼び出すと、一時的なリダイレクトを意味するHTTPステータスコード302がブラウザに返され、ブラウザはそれに基いてリダイレクトを行います。別のステータスコード (301: 恒久的なリダイレクト) に変更するには:status
オプションを使用します。
redirect_to photos_path, status: 301
render
の:status
オプションの場合と同様、redirect_to
の:status
もヘッダーを指定する時に数値の他にシンボルも使用できます。
render
とredirect_to
の違いときおり、redirect_to
を一種のgoto
コマンドとして理解している開発初心者を見かけます。Railsコードの実行位置をある場所から別の場所に移動するコマンドであると考えているわけです。これは 正しくありません 。redirect_to
を実行した後、コードはそこで実行を終了し、ブラウザからの次のリクエストを待ちます (通常のスタンバイ状態)。その直後、redirect_to
でブラウザに送信したHTTPステータスコード302に従って、ブラウザから別のURLへのリクエストがサーバーに送信され、サーバーはそのリクエストを改めて処理します。それ以外のことは行っていません。
render
とredirect_to
の違いを以下のアクションで比較してみましょう。
def index @books = Book.all end def show @book = Book.find_by(id: params[:id]) if @book.nil? render action: "index" end end
上のフォームのコードでは、@book
インスタンス変数がnil
の場合に問題が生じる可能性があります。render :action
は、対象となるアクションのコードを実行しないことを覚えておいてください。このため、index
ビューでおそらく必要となる@books
インスタンス変数には何も設定されず、空の蔵書リストが表示されてしまいます。これを修正する方法のひとつは、renderをredirectに変更することです。
def index @books = Book.all end def show @book = Book.find_by(id: params[:id]) if @book.nil? redirect_to action: :index end end
上のコードであれば、ブラウザから改めてindexページにリクエストが送信されるので、index
メソッドのコードが正常に実行されます。
上のコードで1つ残念な点があるとすれば、ブラウザとのやりとりが1往復増えることです。ブラウザから/books/1
に対してshowアクションが呼び出され、コントローラが本が1冊もないことを検出すると、コントローラはブラウザに対してステータスコード302 (リダイレクト) レスポンスを返し、/books/
に再度アクセスするようブラウザに指令を出します。ブラウザはこの指令に応じ、このコントローラのindex
アクションを呼び出すためのリクエストを改めてサーバーに送信します。そしてコントローラはこのリクエストを受けてデータベースからすべての蔵書リストを取り出し、indexテンプレートをレンダリングして出力結果をブラウザに送り返すと、ブラウザで蔵書リストが表示されます。
このやりとりの増加による遅延は、小規模なアプリケーションであればおそらく問題になりませんが、遅延が甚だしくなってきた場合にはこの点を改める必要があるかもしれません。ブラウザとのやりとりを増やさないように工夫した例を以下に示します。
def index @books = Book.all end def show @book = Book.find_by(id: params[:id]) if @book.nil? @books = Book.all flash.now[:alert] = "Your book was not found" render "index" end end
上のコードの動作は次のとおりです。指定されたidを持つ本が見つからない場合は、モデル内のすべての蔵書リストを@books
インスタンス変数に保存します。続いてflashによる警告メッセージを追加し、さらにindex.html.erb
テンプレートを直接レンダリングしてから出力結果をブラウザに送り返します。
head
でヘッダのみのレスポンスを生成するhead
メソッドを使用することで、ヘッダだけで本文 (body) のないレスポンスをブラウザに送信できます。head
メソッドには、HTTPステータスコードを示す多くのシンボルを引数として指定できます (参照テーブル 参照)。オプションの引数はヘッダ名と値をペアにしたハッシュ値として解釈されます。たとえば、以下のコードはエラーヘッダーのみのレスポンスを返すことができます。
head :bad_request
上のコードによって以下のヘッダーが生成されます。
HTTP/1.1 400 Bad Request Connection: close Date: Sun, 24 Jan 2010 12:15:53 GMT Transfer-Encoding: chunked Content-Type: text/html; charset=utf-8 X-Runtime: 0.013483 Set-Cookie: _blog_session=...snip...; path=/; HttpOnly Cache-Control: no-cache
以下のように、ヘッダーに別の情報を含めることもできます。
head :created, location: photo_path(@photo)
上のコードの結果は以下のようになります。
HTTP/1.1 201 Created Connection: close Date: Sun, 24 Jan 2010 12:16:44 GMT Transfer-Encoding: chunked Location: /photos/1 Content-Type: text/html; charset=utf-8 X-Runtime: 0.083496 Set-Cookie: _blog_session=...snip...; path=/; HttpOnly Cache-Control: no-cache
Railsがビューからレスポンスを出力するときには、そのビューには現在のレイアウトも組み込まれます。現在のレイアウトを探索するときのルールは、本ガイドで既に説明したものが使用されます。レイアウト内では、さまざまな出力の断片を組み合わせて最終的なレスポンス出力を得るための3つのツールを利用できます。
yield
and content_for
アセットタグヘルパーが提供するメソッドは、フィード、JavaScript、スタイルシート、画像、動画および音声のビューにリンクするHTMLを生成するためのものです。Railsでは以下の6つのアセットタグヘルパーが利用できます。
auto_discovery_link_tag
javascript_include_tag
stylesheet_link_tag
image_tag
video_tag
audio_tag
これらのタグは、レイアウトや別のビューで使用することもできます。このうち、auto_discovery_link_tag
、javascript_include_tag
、stylesheet_link_tag
はレイアウトの<head>
セクションで使用するのが普通です。
これらのアセットタグヘルパーは、指定の場所にアセットがあるかどうかを 検証しません 。
auto_discovery_link_tag
を使用してフィードにリンクするauto_discovery_link_tag
ヘルパーを使用すると、多くのブラウザやフィードリーダーでRSSフィードやAtomフィード、JSONフィードを検出できるHTMLが生成されます。このメソッドが受け取れる引数は、リンクの種類 (:rss
または:atom
、:json
)、url_forで渡されるオプションのハッシュ、およびタグのハッシュです。
<%= auto_discovery_link_tag(:rss, {action: "feed"}, {title: "RSS Feed"}) %>
auto_discovery_link_tag
では以下の3つのタグオプションが使用できます。
:rel
はリンク内のrel
値を指定します。デフォルト値は "alternate" です。:type
はMIMEタイプを明示的に指定したい場合に使用します。通常、Railsは適切なMIMEタイプを自動的に生成します。:title
はリンクのタイトルを指定します。デフォルト値は:type
値を大文字にしたものです ("ATOM" や "RSS" など)。javascript_include_tag
を使用してJavaScriptファイルにリンクするjavascript_include_tag
ヘルパーは、指定されたソースごとにHTML script
タグを返します。
Railsでアセットパイプライン を有効にしている場合、JavaScriptへのリンク先は旧Railsのpublic/javascripts
ではなく/assets/javascripts/
になります。その後このリンクはアセットパイプラインによって利用可能になります。
Railsアプリケーション内やRailsエンジン内のJavaScriptファイルは、app/assets
、lib/assets
、vendor/assets
のいずれかの場所に置かれます。これらの置き場所の詳細については、アセットパイプラインガイドの「アセットの編成」 を参照してください。
好みに応じて、ドキュメントルートからの相対フルパスやURLを指定することもできます。たとえば、app/assets
、lib/assets
、またはvendor/assets
の下にあるjavascripts
の下にあるJavaScriptファイルにリンクしたい場合は以下のようにします。
<%= javascript_include_tag "main" %>
上のコードにより、以下のようなscript
タグが出力されます。
<script src='/assets/main.js'></script>
このアセットへのリクエストは、Sprockets gemによって提供されます。
複数のファイルにアクセスしたい場合 (app/assets/javascripts/main.js
とapp/assets/javascripts/columns.js
など) は以下のようにします。
<%= javascript_include_tag "main", "columns" %>
app/assets/javascripts/main.js
とapp/assets/javascripts/photos/columns.js
を含めたい場合は以下のようにします。
<%= javascript_include_tag "main", "/photos/columns" %>
http://example.com/main.js
を含めるには以下のようにします。
<%= javascript_include_tag "http://example.com/main.js" %>
stylesheet_link_tag
を使用してCSSファイルにリンクするstylesheet_link_tag
ヘルパーは、提供されたソースごとにHTML <link>
タグを返します。
Railsでアセットパイプラインを有効にしている場合、このヘルパーは/assets/stylesheets/
へのリンクを生成します。その後このリンクはSprockets gemによって処理されます。スタイルシートファイルは、app/assets
、lib/assets
、またはvendor/assets
のいずれかの場所に置かれます。
ドキュメントルートからの相対フルパスやURLを指定することもできます。たとえば、app/assets
、lib/assets
、またはvendor/assets
の下にあるstylesheets
の下にあるスタイルシートファイルにリンクしたい場合は以下のようにします。
<%= stylesheet_link_tag "main" %>
app/assets/railsguides/stylesheets/main.css
とapp/assets/stylesheets/columns.css
を含めるには、以下のようにします。
<%= stylesheet_link_tag "main", "columns" %>
app/assets/railsguides/stylesheets/main.css
とapp/assets/stylesheets/photos/columns.css
を含めるには以下のようにします。
<%= stylesheet_link_tag "main", "photos/columns" %>
http://example.com/main.css
を含めるには以下のようにします。
<%= stylesheet_link_tag "http://example.com/main.css" %>
デフォルトでは、stylesheet_link_tag
によって作成されるリンクにはmedia="screen" rel="stylesheet"
という属性が含まれます。適切なオプション (:media
, :rel
) を使用することで、これらのデフォルト値を上書きできます。
<%= stylesheet_link_tag "main_print", media: "print" %>
image_tag
を使用して画像にリンクするimage_tag
は、特定のファイルを指すHTML <img />
タグを生成します。デフォルトでは、ファイルはpublic/images
以下から読み込まれます。
画像ファイルの拡張子は省略できません。
<%= image_tag "header.png" %>
好みに応じて、画像ファイルへのパスを直接指定することもできます。
<%= image_tag "icons/delete.gif" %>
ハッシュ形式で与えられたHTMLオプションを追加することもできます。
<%= image_tag "icons/delete.gif", {height: 45} %>
ユーザーがブラウザで画像を非表示にしている場合、alt属性のテキストを表示することができます。alt属性が明示的に指定されていない場合は、ファイル名がaltテキストとして使用されます。このときファイル名の先頭は大文字になり、拡張子は取り除かれます。たとえば、以下の2つのimage_tagヘルパーは同じコードを返します。
<%= image_tag "home.gif" %> <%= image_tag "home.gif", alt: "Home" %>
"{幅}x{高さ}"という形式で特殊なsizeタグを指定することもできます。
<%= image_tag "home.gif", size: "50x20" %>
上の特殊タグ以外にも、:class
や:id
や:name
などの標準的なHTMLオプションを最終的にハッシュにしたものを引数として与えることができます。
<%= image_tag "home.gif", alt: "Go Home", id: "HomeImage", class: "nav_bar" %>
video_tag
を使用してビデオにリンクするvideo_tag
ヘルパーは、指定されたファイルを指すHTML 5 <video>
タグを生成します。デフォルトでは、ファイルはpublic/videos
から読み込まれます。
<%= video_tag "movie.ogg" %>
上のコードによって以下が生成されます。
<video src="/videos/movie.ogg" />
image_tag
の場合と同様、絶対パスまたはpublic/videos
ディレクトリからの相対パスを指定できます。さらに、image_tag
の場合と同様に、size: "#{幅}x#{高さ}"
オプションを指定することもできます。ビデオタグでは、id
やclass
などのHTMLオプションを末尾で自由に指定することもできます。
ビデオタグでは、<video>
HTMLオプションを以下のようなHTMLオプションハッシュ形式で指定することもできます。
poster: "image_name.png"
は、ビデオ再生前にビデオの位置に表示しておきたい画像を指定します。autoplay: true
は、ページの読み込み時にビデオを再生します。loop: true
は、ビデオを最後まで再生し終わったらループします。controls: true
は、ブラウザが提供するビデオ制御機能を使用できるようにします。autobuffer: true
は、ページ読み込み時にすぐ再生できるようにビデオを事前に読み込んでおきます。video_tag
にビデオファイルの配列を渡すことで、複数のビデオを再生することもできます。
<%= video_tag ["trailer.ogg", "movie.ogg"] %>
上のコードによって以下が生成されます。
<video> <source src="/videos/trailer.ogg"> <source src="/videos/movie.ogg"> </video>
audio_tag
を使用して音声ファイルにリンクするaudio_tag
は、指定されたファイルを指すHTML 5 <audio>
タグを生成します。デフォルトでは、これらのファイルはpublic/audios
以下から読み込まれます。
<%= audio_tag "music.mp3" %>
好みに応じて、音声ファイルへのパスを直接指定することもできます。
<%= audio_tag "music/first_song.mp3" %>
:id
や:class
などのオプションをハッシュ形式で指定することもできます。
video_tag
の場合と同様、audio_tag
にも以下の特殊オプションがあります。
autoplay: true
はページ読み込み時に音声ファイルを再生します。controls: true
は、ブラウザが提供する音声ファイル制御機能を使用できるようにします。autobuffer: true
は、ページ読み込み時にすぐ再生できるように音声ファイルを事前に読み込んでおきます。yield
を理解するyield
メソッドは、レイアウトのコンテキストでビューを挿入すべき場所を指定するのに使用します。yield
の最も単純な使用法は、yield
を1つだけ使用して、現在レンダリングされているビューのコンテンツ全体をその場所に挿入するというものです。
<html> <head> </head> <body> <%= yield %> </body> </html>
yield
を行なう領域を複数使用するレイアウトを作成することもできます。
<html> <head> <%= yield :head %> </head> <body> <%= yield %> </body> </html>
ビューのメイン部分は常に「名前のない」yield
としてレンダリングされます。コンテンツを名前付きのyield
としてレンダリングするには、content_for
メソッドを使用します。
content_for
を使用するcontent_for
メソッドを使用することで、コンテンツを名前付きのyield
ブロックとしてレイアウトに挿入できます。たとえば、以下のビューのレンダリング結果は上で紹介したレイアウト内に挿入されます。
<% content_for :head do %> <title>A simple page</title> <% end %> <p>Hello, Rails!</p>
このページのレンダリング結果がレイアウトに挿入されると、最終的に以下のHTMLが出力されます。
<html> <head> <title>A simple page</title> </head> <body> <p>Hello, Rails!</p> </body> </html>
content_for
メソッドは、たとえばレイアウトが「サイドバー」や「フッター」などの領域に分かれていて、それらに異なるコンテンツを挿入したいような場合に大変便利です。あるいは、多くのページで使用する共通のヘッダーがあり、このヘッダーに特定のページでのみJavaScriptやCSSファイルを挿入したい場合にも便利です。
部分テンプレートは通常単にパーシャルと呼ばれます。パーシャルは、上とは異なる方法でレンダリング処理を扱いやすい単位に分割するためのしくみです。パーシャルを使用すると、レスポンスで表示するページの特定部分をレンダリングするためのコードを別ファイルに保存しておくことができます。
パーシャルをビューの一部に含めて出力するには、ビュー内でrender
メソッドを使用します。
<%= render "menu" %>
レンダリング中のビュー内に置かれている上のコードは、その場所で_menu.html.erb
という名前のファイルをレンダリングします。パーシャルファイル名の冒頭にはアンダースコアが付いていることにご注意ください。これは通常のビューと区別するために付けられています。ただしrenderで呼び出す際にはこのアンダースコアは不要です。以下のように、他のフォルダの下にあるパーシャルを呼び出す際にもアンダースコアは不要です。
<%= render "shared/menu" %>
上のコードは、app/views/shared/_menu.html.erb
パーシャルの内容をその場所でレンダリングします。
パーシャルの使用方法の1つは、パーシャルを一種のサブルーチンのようにみなすことです。詳細な表示内容をパーシャル化してビューから追い出し、コードを読みやすくします。例として、以下のようなビューがあるとします。
<%= render "shared/ad_banner" %> <h1>Products</h1> <p>Here are a few of our fine products:</p> ... <%= render "shared/footer" %>
上のコードの_ad_banner.html.erb
パーシャルと_footer.html.erb
パーシャルに含まれるコンテンツは、アプリケーションの多くのページと共有できます。あるページを開発中、パーシャルの部分については詳細を気にせずに済みます。
本ガイドの直前のセクションで説明したように、yield
はレイアウトを簡潔に保つ上で極めて強力なツールです。これは純粋なRubyなのでほぼどこでも利用できることを頭に置いておきましょう。たとえばyield
を用いると、多くの類似リソース向けのフォームレイアウト定義をDRYに書けます。
users/index.html.erb
<%= render "shared/search_filters", search: @q do |f| %> <p> Name contains: <%= f.text_field :name_contains %> </p> <% end %>
roles/index.html.erb
<%= render "shared/search_filters", search: @q do |f| %> <p> Title contains: <%= f.text_field :title_contains %> </p> <% end %>
shared/_search_filters.html.erb
<%= form_for(search) do |f| %> <h1>Search form:</h1> <fieldset> <%= yield f %> </fieldset> <p> <%= f.submit "Search" %> </p> <% end %>
すべてのページで共有されているコンテンツであれば、パーシャルをレイアウトで使用することができます。
ビューにレイアウトがあるのと同様、パーシャルでも独自のレイアウトファイルを使用することができます。たとえば、以下のようなパーシャルを呼び出すとします。
<%= render partial: "link_area", layout: "graybar" %>
上のコードは、_link_area.html.erb
という名前のパーシャルを探し、_graybar.html.erb
という名前のレイアウトを使用してレンダリングを行います。パーシャルレイアウトは、対応する通常のパーシャルと同様、名前の先頭にアンダースコアを追加する必要があります。そして、パーシャルとそれに対応するパーシャルレイアウトは同じディレクトリに置く必要があります。パーシャルレイアウトはlayouts
フォルダーには置けませんのでご注意ください。
:layout
などの追加オプションを渡す場合は、:partial
オプションを明示的に指定する必要がある点にもご注意ください。
パーシャルにローカル変数を引数として渡し、パーシャルをさらに強力かつ柔軟にすることもできます。たとえば、newページとeditページの違いがごくわずかしかないのであれば、この手法を使用してコードの重複を解消することができます。
new.html.erb
<h1>New zone</h1> <%= render partial: "form", locals: {zone: @zone} %>
edit.html.erb
<h1>Editing zone</h1> <%= render partial: "form", locals: {zone: @zone} %>
_form.html.erb
<%= form_for(zone) do |f| %> <p> <b>Zone name</b><br> <%= f.text_field :name %> </p> <p> <%= f.submit %> </p> <% end %>
上の2つのビューでは同じパーシャルがレンダリングされますが、Action Viewのsubmitヘルパーはnewアクションの場合には"Create Zone"を返し、editアクションの場合は"Update Zone"を返します。
どのパーシャルにも、パーシャル名からアンダースコアを取り除いた名前を持つローカル変数が与えられます。:object
オプションを使用することで、このローカル変数にオブジェクトを渡すことができます。
ローカル変数を特定の状況に限ってパーシャルに渡すには、local_assigns
を使います。
index.html.erb
<%= render user.articles %>
show.html.erb
<%= render article, full: true %>
_article.html.erb
<h2><%= article.title %></h2> <% if local_assigns[:full] %> <%= simple_format article.body %> <% else %> <%= truncate article.body %> <% end %>
このようにして、ローカル変数をすべて宣言する必要なしにパーシャルを使えるようになります。
どのパーシャルにも、パーシャル名と同じ名前(冒頭のアンダースコアの有無だけ異なる)のローカル変数が1つずつあります。object
オプションを使うと、このローカル変数にオブジェクトを1つ渡せます。
<%= render partial: "customer", object: @new_customer %>
上のcustomer
パーシャル呼び出しでは、customer
ローカル変数は親のビューの@new_customer
変数を指します。
あるモデルのインスタンスをパーシャルとしてレンダリングするのであれば、以下のような略記法を使用できます。
<%= render @customer %>
上のコードでは、@customer
インスタンス変数にCustomer
モデルのインスタンスが含まれているとします。この場合レンダリングには_customer.html.erb
パーシャルが使用され、このパーシャルにはcustomer
ローカル変数が渡されます。このcustomer
ローカル変数は、親ビューにある@customer
インスタンス変数を指します。
パーシャルはデータの繰り返し (コレクション) を出力する場合にもきわめて便利です。:collection
オプションを使用してパーシャルにコレクションを渡すと、コレクションのメンバごとにパーシャルがレンダリングされて挿入されます。
index.html.erb
<h1>Products</h1> <%= render partial: "product", collection: @products %>
_product.html.erb
<p>Product Name: <%= product.name %></p>
パーシャルを呼び出す時に指定するコレクションが複数形の場合、パーシャルの個別のインスタンスから、出力するコレクションの個別のメンバにアクセスが行われます。このとき、パーシャル名に基づいた名前を持つ変数が使用されます。上の場合、パーシャルの名前は_product
であり、この_product
パーシャル内でproduct
という名前の変数を使用して、出力されるインスタンスを取得できます。
このメソッドには略記法もあります。@products
がproduct
インスタンスのコレクションであるとすると、index.html.erb
に以下のように書くことで同じ結果を得られます。
<h1>Products</h1> <%= render @products %>
使用するパーシャル名は、コレクション内のモデル名に基いて決定されます。実は、メンバが一様でない (さまざまな種類のメンバが入り混じった) コレクションにも上の方法を使用できます。この場合、コレクションのメンバに応じて適切なパーシャルが自動的に選択されます。
index.html.erb
<h1>Contacts</h1> <%= render [customer1, employee1, customer2, employee2] %>
customers/_customer.html.erb
<p>Customer: <%= customer.name %></p>
employees/_employee.html.erb
<p>Employee: <%= employee.name %></p>
上のコードでは、コレクションのメンバに応じて、customerパーシャルまたはemployeeパーシャルが自動的に選択されます。
コレクションが空の場合、render
はnilを返します。以下のような簡単な方法でもよいので、代わりのコンテンツを表示するようにしましょう。
<h1>Products</h1> <%= render(@products) || "There are no products available." %>
パーシャル内のローカル変数をカスタマイズしたい場合は、パーシャルの呼び出し時に:as
オプションを指定します。
<%= render partial: "product", collection: @products, as: :item %>
上のように変更することで、@products
コレクションのインスタンスにitem
という名前のローカル変数経由でアクセスできます。
locals: {}
オプションを使用することで、レンダリング中のどのパーシャルにも任意の名前のローカル変数を渡すことができます。
<%= render partial: "product", collection: @products, as: :item, locals: {title: "Products Page"} %>
上の場合、title
という名前のローカル変数に"Products Page"という値が含まれており、パーシャルからこの値にアクセスできます。
コレクションによって呼び出されるパーシャル内でカウンタ変数を使用することもできます。このカウンタ変数は、パーシャルのタイトル名の後ろに_counter
を追加した名前になります。たとえば、パーシャル内で@products
をレンダリングすると、_product.html.erb
からproduct_counter
変数を参照できます。product_counter
変数は、それを囲むビュー内でレンダリングされた回数を示します。
:spacer_template
オプションを使用することで、メインパーシャルのインスタンスと交互にレンダリングされるセカンドパーシャルを指定することもできます。
<%= render partial: @products, spacer_template: "product_ruler" %>
上のコードでは、_product
パーシャルと_product
パーシャルの合間に_product_ruler
パーシャル (引数なし) をレンダリングします。
コレクションをレンダリングするときにも:layout
オプションを指定できます。
<%= render partial: "product", collection: @products, layout: "special_layout" %>
このレイアウトは、コレクション内の各項目をレンダリングするたびに一緒にレンダリングされます。パーシャル内の場合と同様、このレイアウトでも現在のオブジェクトと(オブジェクト名)_counter変数を使用できます。
特定のコントローラをサポートするために、アプリケーションの標準レイアウトとの違いがごくわずかしかないようなレイアウトを使いたくなることがあります。ネストしたレイアウト (サブテンプレートと呼ばれることもあります) を使用することで、メインのレイアウトを複製して編集したりせずにこれを実現できます。例:
以下のApplicationController
レイアウトがあるとします。
app/views/layouts/application.html.erb
<html> <head> <title><%= @page_title or "Page Title" %></title> <%= stylesheet_link_tag "layout" %> <style><%= yield :stylesheets %></style> </head> <body> <div id="top_menu">Top menu items here</div> <div id="menu">Menu items here</div> <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div> </body> </html>
NewsController
によって生成されるページでは、トップメニューを隠して右メニューを追加したいとします。
app/views/layouts/news.html.erb
<% content_for :stylesheets do %> #top_menu {display: none} #right_menu {float: right; background-color: yellow; color: black} <% end %> <% content_for :content do %> <div id="right_menu">Right menu items here</div> <%= content_for?(:news_content) ? yield(:news_content) : yield %> <% end %> <%= render template: "layouts/application" %>
以上でおしまいです。Newsビューで新しいレイアウトが使用されるようになり、トップメニューが隠されて"content" divタグ内に右メニューが新しく追加されました。
これと同じ結果を得られるサブテンプレートの使用法はこの他にもさまざまなものが考えられます。ネスティングレベルには制限がない点にご注目ください。たとえばNewsレイアウトで新しいレイアウトを使用するために、render template: 'layouts/news'
経由でActionView::render
メソッドを使用することもできます。News
レイアウトをサブテンプレート化するつもりがないのであれば、content_for?(:news_content) ? yield(:news_content) : yield
を単にyield
に置き換えれば済みます。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。