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

この再定義では、rssindexを除く全てのアクションで、productレイアウトが使用されます。

2.2.12.4 レイアウトの継承

レイアウトの再定義は、一般的により深い階層での再定義ほど優先されて上書きされます。 例えば、

application_controller.rb
class ApplicationController < ActionController::Base
  layout "main"
end
posts_controller.rb
class PostsController < ApplicationController
end
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#indexlayoutレイアウトを使用します。
  • 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/1showアクションにリクエストし、 コントローラーは検索の結果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)
  • yieldcontent_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_tagjavascript_include_tagstylesheet_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つになります。

:rel
リンクのrel値を指定します。デフォルトは"alternate"です。
:type
MIMEタイプを指定して明確にします。 Railsは自動的に適切なMIMEタイプを生成します。
:title
リンクのtitleを指定します。 デフォルトの値は:type値の大文字で、例えば"ATOM"または"RSS"になります。

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/assetslib/assetsvendor/assets これらの場所の詳細については、 このガイドのアセットパイプライン内のアセットの構成にて説明しています。

documentルートからの相対のフルパス、またはURL、好きなものを指定することが可能です。 例えば、app/assetslib/assetsvendor/assetsのいずれかの中の、 javascriptsという名前のディレクトリ内の、javaScriptファイルへのリンクであれば、下記のように指定します。

<%= javascript_include_tag "main" %>

Railsはこれをscriptタグでこのように出力します。

<script src='/assets/main.js'></script>

アセットへのこのリクエストは、Sprocketsgemによって提供されます。

同時に、app/assets/javascripts/main.jsapp/assets/javascripts/columns.jsのように、 複数のファイルを含めたい場合は下記のように指定します。

<%= javascript_include_tag "main", "columns" %>

app/assets/javascripts/main.jsapp/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/へのリンクを生成します。 このリンクは、Sprocketsgemによって処理されます。 CSSファイルは、次の3つの場所のいずれから選択されます。
app/assetslib/assetsvendor/assets

<%= stylesheet_link_tag "main" %>

app/assets/stylesheets/main.cssapp/assets/stylesheets/columns.cssを含めるには、 下記のように指定します。

<%= stylesheet_link_tag "main", "columns" %>

app/assets/stylesheets/main.cssapp/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オプション(idclass他)も指定することが出来ます。

動画タグはまた、HTMLオプションのハッシュを通して、下記を含む全ての<video>のHTMLオプションがサポートされています。

poster:
"(画像名).png"を指定することで、再生が開始される前に動画上に指定した画像が表示されます。
autoplay:
trueにすると、ページを読み込んだ際に再生が開始されます。
loop:
trueにすると、動画が繰り返し再生されます。
controls:
trueにすると、ユーザーのための動画を制御するインターフェースがブラウザから提供されます。
autobuffer:
trueにすると、ユーザーがページを読み込んだ際に、動画ファイルのプリロード(先読み)を行います。

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独自のオプションが存在します。

autoplay:
trueにすると、ページを読み込んだ際に再生が開始されます。
controls:
trueにすると、ユーザーのための音声を制御するインターフェースがブラウザから提供されます。
autobuffer:
trueにすると、ユーザーがページを読み込んだ際に、動画ファイルのプリロード(先読み)を行います。

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ページのような異なるコンテンツで重複される部分を、 このテクニックを使ってこの重複を解決することが可能です。

new.html.erb
<h1>New zone</h1>
<%= error_messages_for :zone %>
<%= render partial: "form", locals: {zone: @zone} %>
edit.html.erb
<h1>Editing zone</h1>
<%= error_messages_for :zone %>
<%= 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 %>

同じ部分テンプレートが両方のビューで描画されますが、 Actionビューのsubmitヘルパーは、newアクションでは"Create Zone"を、editアクションでは"Update Zone"を返します。

また、各部分テンプレートは、部分テンプレートと同じローカル変数を持ちます。(アンダースコアは除く) :objectオプションを通して、このローカル変数にオブジェクトを渡すことが出来ます。

<%= render partial: "customer", object: @new_customer %>

customer部分テンプレートは親となるビューから、 @new_customercustomer変数として参照するようになります。

もし、部分テンプレート内へ描画するためのモデルのインスタンスを持っていれば、 下記のような省略文を使用することが出来ます。

<%= render @customer %>

@customerインスタンス変数には、Customerモデルのインスタンスが含まれるとみなされ、 _customer.html.erbが描画に使用されます。 また、ローカル変数customerが部分テンプレートに渡され、 親となるビューの@customerインスタンス変数が参照されます。

3.4.5 コレクションの描画

部分テンプレートは、コレクションの描画で非常に便利です。 :collectionオプションを通して、部分テンプレートにコレクションを渡すと、 コレクションの各項目それぞれ1つずつの部分テンプレートとして挿入されます。

index.html.erb
<h1>Products</h1>
<%= render partial: "product", collection: @products %>
_product.html.erb
<p>Product Name: <%= product.name %></p>

部分テンプレートが複数のコレクションから呼び出された場合、 その後、部分テンプレートの個々のインスタンスは、 部分テンプレートにちなんだ名前が付けられた変数を介して、 レンダリングされるコレクションの項目にアクセスすることができます。 このケースでは、部分テンプレートは_productであり、 productを参照することで、描画されるインスタンスを参照することが出来ます。

また、これの略記も存在します。 @productsは、productインスタンスのコレクションとみなされ、 index.html.erbを下記のように書いて、同じ結果を得ることが可能です。

<h1>Products</h1>
<%= render @products %>

Railsはコレクション内のモデル名から、使用する部分テンプレートの名前を決定します。 実際、この方法で異なるコレクションから作成することも可能で、 Railsはコレクションの各項目に適した部分テンプレートを選択します。

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>

このケースでは、Railsはコレクションの各項目に適したcustomer、 またはemployeeのいずれかの部分テンプレートを使用します。

コレクションが空の場合、rendernilを返すので、 状況に合わせて内容を切り替えるようにしておくべきです。

<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のレイアウトだと仮定します。

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の内部で新しい右メニューが追加されている新しいレイアウトを使用します。

このテクニックを使えば、異なるサブテンプレートを使用した別の方法で、同様の結果を得ることも可能です。 入れ子階層には、制限がないことに注意してください。 1つは、新しいNewsレイアウトを使用するために、render template: 'layouts/news'を通して、 ActionView::renderメソッドを使用することです。 もし、Newsレイアウトに対し、サブテンプレートを使用しないのであれば、 content_for?(:news_content) ? yield(:news_content) : yieldを単にyieldに差し替えてください。

 Back to top

© 2010 - 2017 STUDIO KINGDOM