#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を作ることもできますが、それはまた別の機会に紹介します。


