#269 Template Inheritance
- Download:
- source codeProject Files in Zip (106 KB)
- mp4Full Size H.264 Video (14.1 MB)
- m4vSmaller H.264 Video (8.77 MB)
- webmFull Size VP8 Video (10.3 MB)
- ogvFull Size Theora Video (19.7 MB)
Rails 3.1の新機能のひとつがテンプレートの継承です。この機能はビューの書き方に革命を起こすというほどではないですが、とても便利な新機能です。今回のエピソードでそのしくみを紹介していきます。
このエピソードのために、各ページに同じナビゲーションを持つアプリケーションを準備しました。
このナビゲーションは今はアプリケーションのレイアウトファイルのdiv
要素内にside
というid
で、静的に定義されています。
<!DOCTYPE html> <html> <head> <title>Store</title> <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tags %> </head> <body> <div id="container"> <% flash.each do |name, msg| %> <%= content_tag :div, msg, :id => "flash_#{name}" %> <% end %> <div id="side"> <strong>Pages</strong> <ul> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Products", products_path %></li> </ul> </div> <%= yield %> <div class="clear"></div> </div> </body> </html>
このナビゲーションを、現在のコントローラに応じて変化するようにカスタマイズしたいと思います。これを実現する方法はいくつかありますが、今回はテンプレートの継承を利用します。そのためにまずナビゲーションをsideという部分テンプレート(partial)に切り出します。
<div id="side"> <%= render "side" %> </div>
この新しい部分テンプレートにアプリケーション内のすべてのコントローラからアクセスできるようにするためには、どこに置けばいいでしょうか? views
ディレクトリ下にshared
というディレクトリを作成してそこに置くか、あるいはlayouts
ディレクトリ下に置くこともできますが、Rails 3.1ではviews
ディレクトリ下のapplication
ディレクトリに置きます。Rails 3.1ではこのディレクトリはデフォルトでは存在しないため、新規に作成します。
<strong>Pages</strong> <ul> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Products", products_path %></li> </ul>
このside
という部分テンプレートがすべてのコントローラから参照可能になります。これは、ビューの継承がコントローラの継承と並行して動作するからです。コントローラはすべてApplicationController
を継承しているので、テンプレートはapplication
ディレクトリから継承されます。
アプリケーションのいずれかのページを読み込むと、ナビゲーションは以前の通りに表示されます。
この部分テンプレートは、アプリケーションのすべてのコントローラにおいて、簡単にオーバーライドできます。方法は、別のコントローラのviews
ディレクトリに_side.html.erb
という部分テンプレートファイルを作成するだけです。この設定をProductsController
に対して行うために、追加のリンクを持つ部分テンプレートを作ります。
<strong>Pages</strong> <ul> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Products", products_path %></li> <li><%= link_to "Manage Products", admin_products_path %></li> </ul>
ProductsController
の下のいずれかのページを再読み込みすると、新しいリンクが表示されます。他のコントローラ下のページを見てみると、以前と同じようにデフォルトのナビゲーションが表示されています。
これで複数のコントローラにまたがって共有されるテンプレートのための標準的な置き場所が決まりました。そのテンプレートをオーバーライドするには、対象となるコントローラのviews
ディレクトリに同じ名前のテンプレートを作成します。
この方法は、ネストが深いコンロトーラでも有効です。今回のアプリケーションではAdmin
名前空間の下にいくつかのコントローラがあり、それらはすべてBaseController
を継承しています。
module Admin class BaseController < ApplicationController end end
このBase
ビューのテンプレートもオーバーライドできます。上のページで“Manage Products”のリンクをクリックすると、/admin/products
ページが表示され、ナビゲーションはデフォルトに戻ります。すべてのadminページのナビゲーションをオーバーライドしたい場合はどうすればいいでしょうか?
/app/views/admin
ディレクトリの下にはbase
ディレクトリがあります。ここにテンプレートを置けば、自動的に他のコントローラに継承されます。そのように設定しつつ、加えて他のサイドテンプレートと区別できるようにリンクを一つ追加します。
<strong>Pages</strong> <ul> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Products", products_path %></li> <li><%= link_to "Manage Products", admin_products_path %></li> <li><%= link_to "Manage Categories", admin_categories_path %></li> </ul>
admin/products
ページを再度読み込むと、base
ディレクトリのテンプレートが適用されているのがわかります。
このテンプレートがAdmin
名前空間のすべてのページで表示されるのは、これらのページがAdmin
名前空間のBaseController
から継承されているからです。
テンプレートの継承は部分テンプレートだけでなく、すべてのビューテンプレートで有効です。app/views/admin
フォルダでは、edit.html.erb
テンプレートが、categories
とproducts
の両方のディレクトリに存在します。どちらも中身のコードは同じです。
<h1>Edit</h1> <%= render 'form' %>
これらのテンプレートの一つを上位のbase
ディレクトリに移動してもう片方を削除すると、両方のコントローラが新しい場所のテンプレートを継承するようになります。両方のページとも以前と同じように動作します。アプリケーションに管理用ページが大量にある場合に、この機能は非常に便利です。これらのページは、ビューに繰り返しがたくさんある場合も多いため、この方法を用いればこれらのテンプレートをbaseコントローラに抽象化して取り出し、必要であれば特定のコントローラについてはオーバーライドすることができます。
テンプレートのオーバーライドの話題が出たついでに、もうひとつのトリックとして、コントローラではなく引数に応じてテンプレートをカスタマイズする方法を紹介します。 例えば、モバイル版のサイトをmobileサブドメイン下で提供したいとしましょう。ビュー参照パスを使うことで、サブドメインに応じて使われるビューを変えることができます。この機能は、Rails 3.1の新機能ではなくRails 2でも動作するのですが、この話題に関連するのでここで紹介します。
アプリケーションのコントローラに、ビューパス(view path)と呼ばれるものがあります。ビューでcontroller.view_paths
を呼び出すと、そのコントローラのビューパスを見ることができます。
<h1>Edit</h1> <%= render 'form' %> <%= controller.view_paths %>
productの編集ページを再度読み込むと、view_paths
がリスト表示されます。(なお、アプリケーションの開発中にサブドメインを簡単に使えるようにPowウェブサーバを使用しています。)
このコントローラはデフォルトでビューパスを一つ持っていますが、更に追加できます。お勧めするのはApplicationController
のbefore_filter
を使用する方法です。
class ApplicationController < ActionController::Base protect_from_forgery before_filter :subdomain_view_path def subdomain_view_path if request.subdomain.present? prepend_view_path "app/views/#{request.subdomain}_subdomain" end end end
before_filter
のメソッドで、サブドメインがある場合には、prepend_view_path
でリクエストのサブドメインに応じて新しいビューパスを追加します。ページを再度読み込むと、この新しいビューパスを見ることができます。
mobileサブドメインがview_paths
の一覧に表示されています。上のURLからmobile
サブドメインを削除してページを再読込みすると、表示から消えます。これを使って現在のサブドメインに応じたビューをカスタマイズできます。views
の下にmobile_subdomain
という新しいディレクトリを作成すれば、対応するデフォルトテンプレートをオーバーライドするビューテンプレートをそこに追加できます。ここに新しく作ったproducts
ディレクトリの下にindex.html.erb
テンプレートを新しく作成すると、このテンプレートはmobileサブドメインの下で表示された時だけ使われます。
<h1>mobile version</h1>
サブドメインなしでこのページを表示すると、デフォルトのページが表示されます
しかしmobileサブドメインを追加すると、デフォルトページがオーバーライドされるので新しいテンプレートが使われます。
テンプレートのオーバーライドについての今回のエピソードは以上です。継承とビューの参照パスによる2つの方法を紹介しました。どちらも高度なカスタマイズが可能で、ビューを抽象化してオーバーライドする方法を提供します。ビューを更にカスタマイズする必要があればview resolverを作ることもできますが、それはまた別の機会に紹介します。