Railsによるレイアウトと描画
このガイドでは、ActionコントローラーとActionビューのレイアウト機能について説明します。 以下の事について学んでいきましょう。
- Rails組み込みの、様々な描画メソッドの使用方法について
- 複数のコンテンツから、レイアウトを構築する方法について
- ビューの部品を再利用して、DRYを実戦する方法について
- 入れ子のレイアウト(sub-templates)の使用方法について
1. 概要:パーツはどのように組み立てられるのか
このガイドでは、MVCのうちのC(コントローラー)とV(ビュー)の相互作用にフォーカスを当てて説明していきます。 ご存知のとおり、コントローラーはRails内のリクエストに関する処理を巧みに取り扱いますが、 通常重いコードの処理はModelに引き継ぎます。 また、ユーザーにレスポンスを送り返す場合は、コントローラーはそれをビューに引き継ぎます。 この引き継ぎ(handoff)こそ、このガイドの主題と言っても過言ではありません。
おおまかには、何をレスポンスとして送るべきなのかを決定し、 それに沿った適切なレスポンスを生成するためのメソッドを呼び出すことになります。 もし、レスポンスとしてビューを返す場合、 Railsはビューをレイアウトでラップしたり、もしかしたらビューの部品を取り込んだりといった、追加の作業をすることになります。 このガイドで、これらの動作について確認していきましょう。
2. レスポンスの生成
ビューのコントローラの観点から、HTTPレスポンスを作成するには、次の3つの方法があります。
-
render
を呼び出すことにより、ブラウザに送り返す完全なレスポンスを作成します。 -
redirect_to
を呼び出すことにより、ブラウザにHTTPのリダイレクトステータスを送ります。 -
head
を呼び出すことにより、HTTPヘッダのみで構成されるレスポンスをブラウザに送ります。
2.1 デフォルトによる描画:ActionでのCoC(設定より規約)
あなたは、Railsの基本理念の1つである"Convention over Configuration(設定より規約)"を耳にしたことがあると思います。 デフォルトの描画処理は、この良例であると言えます。 デフォルト処理によって、Railsのコントローラーは正当な経路に一致した名前のビューを自動的に描画します。 例えば、もしBooksControllerクラスが次のようになっており、
class BooksController < ApplicationController
end
routesファイルは次のように書かれており、
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モデルとindexアクションをBooksControllerに追加してみましょう。
class BooksController < ApplicationController
def index
@books = Book.all
end
end
CoCの原則に従い、indexアクションの最後にrenderを明示的にしていないことに注意してください。 このルールでは、明示的にコントローラーのアクションの最後まで描画の指定をしていない場合、 Railsが自動的にコントローラのビューのパスで、(アクション名).html.erbテンプレートを探し、それを描画に使用します。 このケースでは、Railsはapp/views/books/index.html.erbファイルを描画します。
もし、ビュー上で全てのbooksのプロパティを表示したければ、 ERBテンプレートを次のようにします。
<h1>Listing Books</h1>
<table>
<tr>
<th>Title</th>
<th>Summary</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @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 "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to "New book", new_book_path %>
実際のレンダリングは、ActionView::TemplateHandlersのサブクラスが行います。 このガイドでは、その処理の詳細について掘り下げることはしませんが、 ビューのファイル拡張子によって、テンプレートハンドラーに選択される事を知っておくことは重要です。 Rails2では最初、標準のERB(HTMLと埋込みRuby)の拡張子は.erbとBuilder(XMLジェネレーター)の.builderでした。
2.2 renderの使用
ほとんどのケースで、ActionController::Base#render
メソッドは、
ブラウザで使用されるための、アプリケーションコンテンツの描画処理の重労働を行ってくれます。
描画の動作をカスタマイズするには様々な方法があります。
デフォルトのビュー、Railsのテンプレート、特定のテンプレート、ファイル、インラインコードを描画することも、
または何も描画しない、といった事も出来ます。
テキスト、JSON、XMLも描画可能です。
また、コンテンツタイプ、レスポンスHTTPステータスも同様に描画することが出来ます。
もし、ブラウザに検査されていないrender
の正確な結果を確認したいのであれば、render_to_string
を使用します。
このメソッドは、render
と同じオプションを必要とし、
ブラウザに送り返すレスポンスの代わりに、文字列を返してくれます。
2.2.1 何も描画しない
おそらくrender
を使って出来る最も単純なことは、
何も描画しないことでしょう。
render nothing: true
もしcURLを使って、このレスポンスを確認すると、 下記のようになると思います。
$ curl -i 127.0.0.1:3000/books
HTTP/1.1 200 OK
Connection: close
Date: Sun, 24 Jan 2010 09:25:18 GMT
Transfer-Encoding: chunked
Content-Type: */*; charset=utf-8
X-Runtime: 0.014297
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
$
空のレスポンス(Cache-Controlの後にデータ無し)が確認できるはずですが、
Railsがレスポンスを「200 OK」としているため、リクエストは成功となります。
render
の:status
オプションを設定することで、このレスポンスを変更することが可能です。
描画を何もしないというのは、ブラウザに送り返したい全てのAjaxリクエストが完了して、承認されたという場合に便利です。(翻訳に自信なし)
もしかしたら、render :nothing
の代わりにhead
メソッドを使用すべきかもしれませんが、
これについては後述します。
これは、より柔軟で明確にHTTPヘッダーのみを生成する機能を提供してくれます。
2.2.2 Actionビューの描画
もし、同じコントローラー内で名前の異なるビューファイルを描画したい場合、
render
にビューの名前を指定することで、それが可能になります。
def update
@book = Book.find(params[:id])
if @book.update(params[:book])
redirect_to(@book)
else
render "edit"
end
end
もし更新に失敗すると、このコントローラー内のupdateアクションは、 同じコントローラーのedit.html.erbテンプレートを描画します。
def update
@book = Book.find(params[:id])
if @book.update(params[:book])
redirect_to(@book)
else
render :edit
end
end
2.2.3 別のコントローラーから、アクションのテンプレートを描画
もし、全く異なるコントローラーのテンプレートを描画したい場合は、アクション内のコードはどのようにすれば良いのでしょうか?
これもrender
を使って行うことが可能で、描画するテンプレートをフルパス(app/viewsの相対)で指定します。
例えば、もしapp/controllers/admin内のAdminProductsController内のコードを実装している場合、
下記の方法で、app/views/products内のテンプレートをアクションの結果として描画することが可能です。
render "products/show"
Railsは、文字列内にスラッシュが含まれていることから、このビューが異なるコントローラーのものだと認識します。
もし、明確にしたいのであれば、:template
オプションを使用することが出来ます。
(これは、Rails2.2とそれ以前に必要とされていました。)
render template: "products/show"
2.2.4 任意のファイルの描画
render
メソッドは、完全なアプリケーション外のビューであっても使用することが可能です。
(2つのRailsアプリケーション間でビューを共有したい場合など)
render "/u/apps/warehouse_app/current/app/views/products/show"
Railsは、文字列がスラッシュから始まることから、この事を判断します。
もし、明確にしたいのであれば、:file
オプションを使用することが出来ます。
(これは、Rails2.2とそれ以前に必要とされていました。)
render file: "/u/apps/warehouse_app/current/app/views/products/show"
:file
オプションは、絶対パスを必要とします。
もちろん、コンテンツをレンダリングするために使用するビューへのアクセス権を持っている必要があります。
デフォルトでは、こうしたファイルの指定は、レイアウト(layout)を使用しないで描画されます。
もし、これをカレントのレイアウト(layout)を使用して描画したい場合、
layout: true
オプションを追加する必要があります。
もし、RailsをWindows上で実行している場合、Windowsのファイル名はUnixのファイル名とはフォーマットが異なるため、
ファイルを描画する場合は:file
オプションを使用すべきです。
2.2.5 3つの方法についてのまとめ
これまでに、描画方法について3つの方法(コントローラー内の別のテンプレートの描画、 別のコントローラーのテンプレートの描画、ファイルシステム上の任意のファイル、 )を確認してきましたが、実際はそれぞれ同じ動作の変異に過ぎません。
事実、BooksControllerクラスのupdateアクションで、bookの更新が失敗した際にeditテンプレートを描画したいとして、 下記の全てのrender呼び出しは、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"
どれを使用するかは、スタイルや慣習の問題ですが、 コードが最もシンプルになる書き方を選ぶべきでしょう。
2.2.6 :inlineを使用した描画
render
メソッドは、完全にビュー無しでも使用することが可能で、
:inline
オプションを使用して、メソッド呼び出しの一部としてERBを提供する事が可能です。
下記も正しいrender
の指定です。
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
このオプションを使用する積極的な理由はありません。 ERBがコントローラーに混在することで、RailsのMVCの枠組みが崩れ、 プロジェクトでの他の開発者にとっても分かりにくいものになってしまいます。 erbはビューに切り分けるようにしましょう。
デフォルトでは、inlineはERBを使用して描画を行います。
:type
オプションを使用して、代わりにBuilderを使用するようにすることが可能です。
render inline: "xml.p {'Horrid coding practice!'}", type: :builder
2.2.7 テキストの描画
render
の:text
オプションを使用することで、
まったくHTMLマークアップされていないプレーンテキストをブラウザに送ることが出来ます。
render text: "OK"
純粋なテキストの描画するは、AjaxまたはHTMLではなく他のフォーマットが求められる際に、 それをレスポンスとして返す場合に重宝されます。
デフォルトでは、:text
オプションを使用する場合、レイアウト(layout)を使用しないで描画されます。
もし、これをカレントのレイアウト(layout)を使用して描画したい場合、
layout: true
オプションを追加する必要があります。
2.2.8 JSONの描画
JSONは多くのAjaxライブラリで使用されるjavaScriptのデータフォーマットです。 Railsは、オブジェクトをJSONに変換するための機能を組み込みでサポートしており、 描画したJSONをブラウザに返してくれます。
render json: @product
描画したいオブジェクトでto_json
を呼び出す必要はありません。
:json
オプションを使用すれば、render
は自動的にto_json
を呼び出してくれます。
2.2.9 XMLの描画
Railsはまた、オブジェクトをXMLに変換する機能も組み込みでサポートしており、 描画したXMLを呼び出し元に返してくれます。
render xml: @product
描画したいオブジェクトでto_xml
を呼び出す必要はありません。
:xml
オプションを使用すれば、render
は自動的にto_xml
を呼び出してくれます。
2.2.10 通常のjavaScriptの描画
Railsは、通常のjavaScriptを描画することも可能です。
render js: "alert('Hello Rails');"
こうすることで、text/javascript
のMIMEタイプで、
提供された文字列をブラウザに送信します。
2.2.11 renderのオプションについて
render
メソッドは、一般的に4つのオプションを受け入れます。
:content_type
:layout
:location
:status
2.2.11.1 :content_typeオプション
デフォルトでは、Rialsは描画の結果としてtext/html
のMIMEコンテンツタイプを提供します。
(もし、:json
オプションなら、application/json
、:xml
オプションなら、application/xml)
これを変更したければ、:content_type
を設定することでそれが可能になります。
render file: filename, content_type: "application/rss"
2.2.11.2 :layoutオプション
render
で最も使用されるオプションで、描画されるコンテンツは、現在のレイアウト(layout)の一部として表示されます。
レイアウトの詳細と使い方については、後述します。
:layout
オプションを使用することで、Railsに現在のアクションで使用するレイアウトのファイルを伝えます。
render layout: "special_layout"
描画にレイアウトを全く使用しないことを伝えることも可能です。
render layout: false
2.2.11.3 :location オプション
:location
オプションを使用して、HTTPのLocationヘッダーを設定することが可能です。
render xml: photo, location: photo_url(photo)
2.2.11.4 :statusオプション
Railsは自動的に、「正しい」とするHTTPステータスコード(多くのケースで200 OK
)のレスポンスを生成します。
:status
オプションを使用して、これを変更することがkな王です。
render status: 500
render status: :forbidden
Railsは、ステータスコードの数値と、下記一覧のステータスと対になるシンボルを認識してくれます。
レスポンスクラス | HTTPステータスコード | シンボル |
---|---|---|
通知 | 100 | :continue |
101 | :switching_protocols | |
102 | :processing | |
成功 | 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 | |
リダイレクト | 300 | :multiple_choices |
301 | :moved_permanently | |
302 | :found | |
303 | :see_other | |
304 | :not_modified | |
305 | :use_proxy | |
306 | :reserved | |
307 | :temporary_redirect | |
308 | :permanent_redirect | |
クライアントエラー | 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 | :request_entity_too_large | |
414 | :request_uri_too_long | |
415 | :unsupported_media_type | |
416 | :requested_range_not_satisfiable | |
417 | :expectation_failed | |
422 | :unprocessable_entity | |
423 | :locked | |
424 | :failed_dependency | |
426 | :upgrade_required | |
423 | :precondition_required | |
424 | :too_many_requests | |
426 | :request_header_fields_too_large | |
サーバエラー | 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 |
2.2.12 レイアウトの参照
レイアウトを探すために、Railsはまず始めにapp/views/layouts
内で、
コントローラーと同じ名前のファイルが無いか探します。
例えば、PhotosController
クラスのアクションであれば、app/views/layouts/photos.html.erbになります。
(または、app/views/layouts/photos.builder)
もし、そのようなコントローラー固有のレイアウトが無ければ、
Railsはapp/views/layouts/application.html.erbまたは、app/views/layouts/application.builderを使用します。
もし、.erb
のレイアウトが無ければ、Railsは存在すれば.builder
のレイアウトを使用します。
Railsはまた、より詳細に個々のコントローラーとアクションにレイアウトを割り当てる方法を提供します。
2.2.12.1 コントローラーへのレイアウトの指定
デフォルトのレイアウトの慣習を上書きするには、コントローラー内でlayout
を再定義します。
例えば、
class ProductsController < ApplicationController
layout "inventory"
#...
end
この再定義によって、productsコントローラーの全てのビューは、 レイアウトにapp/views/layouts/inventory.html.erbを使用するようになります。
アプリケーション全体のレイアウトを指定するには、
layoutの再定義をApplicationController
内で行います。
class ApplicationController < ActionController::Base
layout "main"
#...
end
この再定義によって、アプリケーション内の全てのビューは、 レイアウトに app/views/layouts/main.html.erbを使用するようになります。
2.2.12.2 実行時にレイアウトを選択
シンボルを使用することで、リクエストが処理されるまでレイアウトの選択を延期することが可能です。
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
この場合、もしユーザーが特別なユーザーであった場合、 productのビューを参照した際にspecialレイアウトが選択されます。
どのレイアウトを選択するかを、Procのようなインラインメソッドでも使用することが可能です。
例えば、もしProcオブジェクトを渡し、そのProcのブロックにcontroller
インスタンスを与えれば、
リクエストを元に使用するレイアウトを決定することが可能です。
class ProductsController < ApplicationController
layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
2.2.12.3 条件付きのレイアウト
レイアウトはコントローラー層で、:only
と:except
オプションがサポートされます。
これらのオプションは、コントローラー内に存在するメソッド名、またはメソッド名の配列を受け取ります。
class ProductsController < ApplicationController
layout "product", except: [:index, :rss]
end
この再定義では、rss
とindex
を除く全てのアクションで、product
レイアウトが使用されます。
2.2.12.4 レイアウトの継承
レイアウトの再定義は、一般的により深い階層での再定義ほど優先されて上書きされます。 例えば、
このようなアプリケーションの場合、
-
一般的にビューは、
main
レイアウトを使用します。 -
PostsController#index
はlayout
レイアウトを使用します。 -
SpecialPostsController#index
は、special
レイアウトを使用します。 -
OldPostsController#show
は、レイアウトを使用しません。 -
OldPostsController#index
は、old
レイアウトを使用します。
2.2.13 重複renderエラーを避ける
遅かれ早かれ、多くの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はspecial_show
ビューに@book
値を差し出し、描画処理を開始します。
ただし、show
アクションの残りのコードの実行を停止せず、
Railsはアクションの最後まで達し、regular_show
ビューの描画が開始され、その結果エラーがスローされます。
解決方法は簡単です。
「1つ」のコードパスで、render
またはredirect
が1度だけ呼ばれることを確認してください。
「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
では、Ruby言語の演算子の優先順位により動作しないので、
&& return
の代わりに、and return
を使用して下さい。
render
が呼び出された場合、render
が行われた事を、
ActionControllerは暗黙的に検出することに注意してください。
下記はエラー無しで動作します。
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
end
special?
のbookであれば、special_show
で設定されたものが描画されます。
その他のbookは、デフォルトのshow
のテンプレートで描画されます。
2.3 redirect_toの使用
HTTPリクエストに応答を返す処理を行うもう一つの方法は、redirect_to
を使うことです。
ご覧になったように、render
は、Railsにレスポンスの構築に使用するビュー(または、他の項目)を教えます。
redirect_to
メソッドが行うことは完全に異なり、
ブラウザに異なるURLのための新しいリクエストを送るように伝えます。
例えば、コード内の何処かからphotosのindexにリダイレクトするには、
次のように呼び出します。
redirect_to photos_url
redirect_to
の引数に、link_to
またはurl_for
で使用できるものと、
同じものが使用可能です。
また、訪問前のページへユーザーを送り返す特殊なリダイレクトも存在します。
redirect_to :back
2.3.1 様々なリダイレクト・ステータスコード
Railsは、redirect_to
が呼ばれた際には、HTTPステータスコード302(一時的に移動)を使用します。
もし、異なるステータスコードを使いたい場合、例えば301(恒久的に移動)であれば、
下記のように、:status
オプションを使うことが可能です。
redirect_to photos_path, status: 301
render
の:status
と同じように、redirect_to
の:status
も、
数値とシンボルどちらも使用することが可能です。
2.3.2 renderとredirect_toの違い
不慣れな開発者は、Railsコード内のredirect_to
を実行を別の場所へ移動する、
goto
コマンドの一種と考えるかもしれませんが、これは正しくありません。
コードの実行は停止し、ブラウザからの新しいリクエストを待つことになります。
次に何をリクエストすべきなのかを、ブラウザにHTTP302ステータスコードを送り返すことによって伝えます。
これらのアクションの違いを考えてみましょう。
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_to
を使用します。
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メソッド内のコードが実行され、問題なく動作するはずです。
2つ目のコードでは、ブラウザは/books/1
のshow
アクションにリクエストし、
コントローラーは検索の結果bookが何も無いことから、ブラウザに302のリダイレクトレスポンスで/books/
に行くように伝え、
往復することを要求します。
ブラウザはレスポンスに応じ、コントローラーにindex
アクションのための新しいリクエスを送り返します。
コントローラーは全てのbooksをデータベースから取得し、indexテンプレートにデータを描画してブラウザに再び送り返し、
その結果がブラウザによってスクリーン上に表示されることになります。
小さなアプリケーションであれば、このレイテンシー(データ転送の遅延)は問題になることは無いでしょうが、 レスポンス時間が重要になるケースでは、大いに考慮すべき内容です。 下記の例を元に、このことについてデモンストレーションしてみましょう。
def index
@books = Book.all
end
def show
@book = Book.find_by_id(params[:id])
if @book.nil?
@books = Book.all
flash[:alert] = "Your book was not found"
render "index"
end
end
このコードでは、指定されたIDのbookが見つからなければ、 モデルから全てのbooksを取得して@booksインスタンス変数へ格納し、 直接index.htmlerbテンプレートを描画します。 また、ブラウザにflashの警告メッセージを表示させることによって、 ユーザーに何が起こったのかを伝えます。
2.4 headを使用してHeaderのみのレスポンスを構築
head
メソッドを使うことによって、ブラウザにヘッダーのみのレスポンスを返すことが可能です。
これは、render :nothing
に比べ、より明確に提供することになります。
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
また、下記は他のHTTPヘッダーによって別の情報を伝える例になります。
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
3. レイアウトの構造
Railsがレスポンスとしてビューを描画する際、このガイドで前述したルールに基づいて探しだされたレイアウト(layout)と結合して、 それが行われます。 レイアウト(layout)を使用する場合、レスポンス全体で出力形式が少しずつ異なる3つのツールを使用することが出来ます。
- アセットタグ(Asset tags)
yield
とcontent_for
- 部分テンプレート(Partials)
3.1 アセットタグ・ヘルパー
アセットタグ・ヘルパーは、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>内で使用されます。
アセットタグ・ヘルパーは指定された場所にアセットが存在するのか検証を行うようなことはしません。 単純に、その場所に存在するものとしてリンクを生成します。
3.1.1 auto_discovery_link_tagによるリンク
auto_discovery_link_tag
ヘルパーは、
多くのブラウザとニュースリーダーで使用可能なRSSやAtomフィードを提供するHTMLを構築します。
これは、リンクのタイプを受け取り(:rss
または:atom
)、
1つ目のオプションのハッシュはurl_forを通して渡され、2つ目のオプションのハッシュはタグの属性として出力するものを指定します。
<%= auto_discovery_link_tag(:rss, {action: "feed"},
{title: "RSS Feed"}) %>
auto_discovery_link_tag:
で利用可能なタグオプションは、
下記の3つになります。
3.1.2 javascript_include_tagによるjavaScriptファイルへのリンク
javascript_include_tag
ヘルパーは、
HTMLのscript
タグを提供された各ソースに返します。
もし、Railsのアセットパイプライン( Asset Pipeline)を有効にしているのであれば、
このヘルパーは以前のバージョンのRialsが使用していたpublic/javascripts
では無く、
/assets/javascripts/
へのリンクを生成します。
このリンクは、アセットパイプラインから提供されます。
Railsアプリケーション、またはRailsエンジン内のjavaScriptファイルは、
次の3つの場所のいずれから選択されます。
app/assets
、lib/assets
、vendor/assets
これらの場所の詳細については、
このガイドのアセットパイプライン内のアセットの構成にて説明しています。
documentルートからの相対のフルパス、またはURL、好きなものを指定することが可能です。
例えば、app/assets
、lib/assets
、vendor/assets
のいずれかの中の、
javascripts
という名前のディレクトリ内の、javaScriptファイルへのリンクであれば、下記のように指定します。
<%= javascript_include_tag "main" %>
Railsはこれを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" %>
3.1.3 stylesheet_link_tagによるCSSファイルへのリンク
stylesheet_link_tag
タグヘルパーは、各供給先にHTMLの<link>タグを返します。
もし、Railsのアセットパイプライン( Asset Pipeline)を有効にしているのであれば、
/assets/stylesheets/
へのリンクを生成します。
このリンクは、Sprockets
gemによって処理されます。
CSSファイルは、次の3つの場所のいずれから選択されます。
app/assets
、lib/assets
、vendor/assets
<%= stylesheet_link_tag "main" %>
app/assets/stylesheets/main.css
とapp/assets/stylesheets/columns.css
を含めるには、
下記のように指定します。
<%= stylesheet_link_tag "main", "columns" %>
app/assets/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" %>
3.1.4 image_tagを使用した画像へのリンク
image_tag
ヘルパーは、指定したファイルの<img />タグを生成します。
デフォルトでは、public/images
へのリンクを生成します。(assets/imagesの間違い?)
画像ファイルの拡張子を指定しなければいけないことに注意してください。
<%= image_tag "header.png" %>
用途に合わせて、次のように画像へのパスを指定することも可能です。
<%= image_tag "icons/delete.gif" %>
追加のHTMLオプションのハッシュを指定することも可能です。
<%= image_tag "icons/delete.gif", {height: 45} %>
ブラウザに画像を表示しない設定にしているユーザー用に使用される、画像のaltテキストを指定することも可能です。 もし、altテキストを指定しなかった場合、デフォルトでファイル名の拡張子無し、キャピタライズ(単語の先頭が大文字)形式にしたものが使用されます。 例えば、下記の2つのimageタグは同じコードになります。
<%= image_tag "home.gif" %>
<%= image_tag "home.gif", alt: "Home" %>
また、"{width}x{height}"
形式の特別なサイズ指定も可能です。
<%= image_tag "home.gif", size: "50x20" %>
上記の特別な形式に加え、:class
、:id
、:name
のような、
標準のHTMLオプションを、最後のハッシュに指定することが可能です。
<%= image_tag "home.gif", alt: "Go Home",
id: "HomeImage",
class: "nav_bar" %>
3.1.5 video_tagによる動画へのリンク
video_tag
ヘルパーは、指定したファイルへのHTML5の<video>タグを生成します。
デフォルトでは、ファイルはpublic/videosへのリンクを生成します。(assets/videosの間違い?)
<%= video_tag "movie.ogg" %>
下記のタグを出力します。
<video src="/videos/movie.ogg" />
image_tag
のリンクへのパスのように、public/videosディレクトリへのパスは、絶対、相対のどちらでも指定可能です。
加えて、サイズの指定もimage_tag
のように"size: #{width}x#{height}"
オプションで指定可能です。
動画タグは、最後のハッシュにHTMLオプション(id
、class
他)も指定することが出来ます。
動画タグはまた、HTMLオプションのハッシュを通して、下記を含む全ての<video>のHTMLオプションがサポートされています。
video_tag
に動画ファイルの配列を渡すことで、複数の動画も指定可能です。
<%= video_tag ["trailer.ogg", "movie.ogg"] %>
上記は次のように出力されます。
<video><source src="trailer.ogg" /><source src="movie.ogg" /></video>
3.1.6 audio_tagによる音声ファイルへのリンク
audio_tag
ヘルパーは、指定されたファイルのHTML5の<audio>タグを構築します。
デフォルトでは、public/audios
のファイルを読み込みます。(assetsではなく、publicフォルダで正しい?)
<%= audio_tag "music.mp3" %>
また、任意のパスの音声ファイルを指定可能です。
<%= audio_tag "music/first_song.mp3" %>
:id
や:class
のような、追加のオプションのハッシュを渡すことも可能です。
video_tag
のように、下記のaudio_tag
独自のオプションが存在します。
3.2 yieldを理解する
レイアウトのコンテキスト内で、yield
はビューからコンテンツを挿入するセクションを識別します。
最も単純な使用方法は、1つのyield
を、現在の描画されるビューの全体のコンテンツの中に挿入することです。
<html>
<head>
</head>
<body>
<%= yield %>
</body>
</html>
また、複数のyield
を埋め込んだレイアウトを作成することも可能です。
<html>
<head>
<%= yield :head %>
</head>
<body>
<%= yield %>
</body>
</html>
ビューのbody部分へは常に名前無しのyield
が描画されます。
名前付きのyield
のコンテンツを描画するには、content_for
メソッドを使用します。
3.3 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ファイルのためのタグを、
共通のレイアウト内のヘッダーやその他の場所に挿入したい場合にも便利です。
3.4 部分テンプレートの使用
部分テンプレート(一般的に"partials"と呼ばれる)は、描画プロセスをより管理しやすくするために、 別のファイルにビューの部分を分割するための仕組みです。 部分テンプレートを使用して、ファイルへのレスポンスの特定の部分を描画するためのコードを、 別のファイルへ移すことが出来ます。
3.4.1 部分テンプレートの命名
ビューの一部分を部分テンプレートとして描画するために、ビュー内でrender
メソッドを次のように指定します。
<%= render "menu" %>
ビューが描画される際に、指定された場所にファイル名_menu.html.erb
のファイルの内容が描画されます。
ファイル名の先頭にアンダースコア(_
)があることに注意してください。
部分テンプレートは、通常のビューと識別するために先頭にアンダースコアを付けます。
別のフォルダの部分テンプレートを適用する場合にも、これが当てはまります。
<%= render "shared/menu" %>
このコードは、app/views/shared/_menu.html.erb
の部分テンプレートを適用します。
3.4.2 単純なビューでの部分テンプレートの使用
部分テンプレートの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
の部分テンプレートに含まれる内容は、
アプリケーション上の多くのページで共有されます。
特定のページで部分テンプレートを使用する際は、それらの詳細な内容について把握しておく必要はありません。
アプリケーション内のすべてのページで共有されるコンテンツは、 レイアウトから直接、部分テンプレートを使用することが可能です。
3.4.3 Partial Layouts
部分テンプレートは、ビューが使用するレイアウトのように独自のレイアウトファイルとして使用可能です。 例えば、下記のように部分テンプレートを呼び出したとすると、
<%= render partial: "link_area", layout: "graybar" %>
これは、_link_area.html.erb
の部分テンプレートを探し、
_graybar.html.erb
レイアウトを使用して描画します。
部分テンプレートのレイアウトは、通常の部分テンプレートと同様に先頭にアンダースコア付きの名前で、
部分テンプレートと同じフォルダーに入れる必要があることに注意してください。(マスターのlayoutsフォルダでは無い)
:layout
のような追加オプションが渡される場合、
:partial
による明確な指定も、必要となることに注意してください。
3.4.4 ローカル変数の受け渡し
また、部分テンプレートにローカル変数を渡すことが可能で、より柔軟でより効率的な事が実現可能になります。 例えば、newページとeditページのような異なるコンテンツで重複される部分を、 このテクニックを使ってこの重複を解決することが可能です。
同じ部分テンプレートが両方のビューで描画されますが、 Actionビューのsubmitヘルパーは、newアクションでは"Create Zone"を、editアクションでは"Update Zone"を返します。
また、各部分テンプレートは、部分テンプレートと同じローカル変数を持ちます。(アンダースコアは除く)
:object
オプションを通して、このローカル変数にオブジェクトを渡すことが出来ます。
<%= render partial: "customer", object: @new_customer %>
customer部分テンプレートは親となるビューから、
@new_customer
をcustomer
変数として参照するようになります。
もし、部分テンプレート内へ描画するためのモデルのインスタンスを持っていれば、 下記のような省略文を使用することが出来ます。
<%= render @customer %>
@customer
インスタンス変数には、Customer
モデルのインスタンスが含まれるとみなされ、
_customer.html.erb
が描画に使用されます。
また、ローカル変数customer
が部分テンプレートに渡され、
親となるビューの@customer
インスタンス変数が参照されます。
3.4.5 コレクションの描画
部分テンプレートは、コレクションの描画で非常に便利です。
:collection
オプションを通して、部分テンプレートにコレクションを渡すと、
コレクションの各項目それぞれ1つずつの部分テンプレートとして挿入されます。
部分テンプレートが複数のコレクションから呼び出された場合、
その後、部分テンプレートの個々のインスタンスは、
部分テンプレートにちなんだ名前が付けられた変数を介して、
レンダリングされるコレクションの項目にアクセスすることができます。
このケースでは、部分テンプレートは_product
であり、
product
を参照することで、描画されるインスタンスを参照することが出来ます。
また、これの略記も存在します。
@products
は、product
インスタンスのコレクションとみなされ、
index.html.erb
を下記のように書いて、同じ結果を得ることが可能です。
<h1>Products</h1>
<%= render @products %>
Railsはコレクション内のモデル名から、使用する部分テンプレートの名前を決定します。 実際、この方法で異なるコレクションから作成することも可能で、 Railsはコレクションの各項目に適した部分テンプレートを選択します。
このケースでは、Railsはコレクションの各項目に適したcustomer
、
またはemployee
のいずれかの部分テンプレートを使用します。
コレクションが空の場合、render
はnil
を返すので、
状況に合わせて内容を切り替えるようにしておくべきです。
<h1>Products</h1>
<%= render(@products) || "There are no products available." %>
3.4.6 ローカル変数
部分テンプレートのカスタムローカル変数名を使用するには、
部分テンプレートを呼ぶ際に、:as
オプションを指定します。
<%= render partial: "product", collection: @products, as: :item %>
この変更により、部分テンプレート内のitem
ローカル変数として、
@products
のインスタンスにアクセスすることが可能になります。
また、locals: {}
オプションを使用して、
任意のローカル変数として部分テンプレートに渡すことも可能です。
<%= render partial: "products", collection: @products,
as: :item, locals: {title: "Products Page"} %>
上記は、部分テンプレート_products.html.erb
が、
@products
コレクションの各項目毎に描画され、
ローカル変数item
としてproduct
インスタンスが渡されます。
また、"Products Page"の値が格納された変数が、ローカル変数title
として渡されます。
Railsは、またコレクションによって呼び出される部分テンプレートで利用可能なカウンター変数を作ります。
カウンター変数名は、コレクション項目名の後ろに_counter
を付けたものになります。
例えば、@products
を描画する場合、部分テンプレート内でproduct_counter
を参照することで、
何回目の描画なのかを知ることが出来きます。
ただし、as: :value
と併用して使用することは出来ません。
また、:spacer_template
オプションを使用することで、
メインの部分テンプレートのインスタンスとの間に、2つ目の部分テンプレートを描画することも可能です。
3.4.7 スペーサー・テンプレート
<%= render partial: @products, spacer_template: "product_ruler" %>
Railsは_product_ruler
部分テンプレート(データを渡さない)を、
各_product
部分テンプレートとの間に描画します。
3.4.8 コレクション部分テンプレート
コレクションの描画時にも、:layout
オプションを使用することが可能です。
<%= render partial: "product", collection: @products, layout: "special_layout" %>
指定したレイアウトは、コレクション内の各項目ごとに部分テンプレートと一緒に描画されます。 オブジェクトと、そのカウンター変数は同様にレイアウト内でも利用可能で、 部分テンプレートと同じ方法で使用します。
3.5 入れ子レイアウトの使用
アプリケーションの作成において、ある特殊なコントローラーをサポートするために、 標準のレイアウトと少しだけ異なるレイアウトが必要になることがあるかもしれません。 標準のレイアウトをコピーして編集するのよりも、入れ子レイアウト(時にサブテンプレート(sub-templates)と呼ばれる)を使用することで、 これを解決することが出来ます。 下記の例で説明します。
下記のものが、ApplicationController
のレイアウトだと仮定します。
NewsController
によって生成されるページでは、
トップメニューを隠し、右メニューを追加したい場合は次のようにします。
Newsビューはトップメニューが隠れ、"content"のDIVの内部で新しい右メニューが追加されている新しいレイアウトを使用します。
このテクニックを使えば、異なるサブテンプレートを使用した別の方法で、同様の結果を得ることも可能です。
入れ子階層には、制限がないことに注意してください。
1つは、新しいNews
レイアウトを使用するために、render template: 'layouts/news'
を通して、
ActionView::render
メソッドを使用することです。
もし、News
レイアウトに対し、サブテンプレートを使用しないのであれば、
content_for?(:news_content) ? yield(:news_content) : yield
を単にyield
に差し替えてください。
© 2010 - 2017 STUDIO KINGDOM