サービスを運営していると、
- Admin以下のページだけちょっとヘッダーを変えたい
- ABテストのために2種類のレイアウトを試してみたい
- キャンペーン用にある期間だけ別のロゴを使いたい
- ユーザー登録ページだけ簡易レイアウトにしたい
などなどの理由で、app/views/layouts/application.html.erb
と大体同じだけど、ちょっと違うというレイアウトが欲しくなります。ただ、コピペでいろんなレイアウトを作ってしまうと後の変更が困難になります。Layoutでもデフォルト値や継承を使って再利用性の高いものを作りましょう。
方法1 Default content_for
あるyieldに対応するcontent_forがセットされなかった時のデフォルト値を定める方法です。
yieldは普通の文字列を返す関数で、値がセットされていなければ""
が返ります。なのでempty?
を使って値がセットされているか調べることができます。
<header>
<% if yield(:header).empty? %>
<%= render 'header' %>
<% else %>
<%= yield :header %>
<% end %>
</header>
方法2 Template Inheritance
Controllerの継承関係がViewのTemplateでも使えるという話です。
とりあえず以下のRails Castを見るのがいいと思います。
http://railscasts.com/episodes/269-template-inheritance
要約すると、共有したいpartial templateはapp/views/shared
等ではなくapp/views/application
下に置きます。例えば_header.rb
という名前のpartialだとすると、以下のようになります。
<%= render 'header' %>
<div class="head">Normal Page</div>
ここでproducts以下のページだけheader部分の内容を変えたいとしたら、以下のようにproducts
ディレクトリの下に同名の_header.html.erb
を作り内容を上書きます。
<div class="head">Production Page</div>
お分かりかと思いますが、これは、以下のようにProductsControllerがApplicationControllerを継承している事を利用しています。
class ProductsController < ApplicationController
この方法は綺麗ですが、LayoutをControllerごとにしか変えられないという問題があります。
方法3 Parent Layout
layoutのテンプレートを親layoutを使って設定する方法です。各yieldの値が設定済みのlayoutを作れます。
基本的には以下のリンクを参照するといいです。
http://m.onkey.org/nested-layouts-in-rails-3
ただこれはrails3.0の方法を示していて、rails3.2では動きません。これを、http://stackoverflow.com/questions/9185344/ を参照して直したのが以下です。
module ApplicationHelper
def parent_layout(layout)
@view_flow.append(:layout, self.output_buffer)
self.output_buffer = render(file: "layouts/#{layout}")
end
end
これを使って、
<h1>Admin Panel</h1>
<div class="admin_navigation">Home | Posts | Users | Assets</div>
<%= yield %>
<% content_for :header do %>
<%= link_to image_tag('logo.png'), admin_root_path %>
<% end %>
<% parent_layout 'application' %>
のようにすれば、layout/application.html.erb
をベースにlayout/admin.html.erb
を作れます。
3つを併用した使用例
これらの方法はどれかを選ぶというよりかは併用すると良いです。
<!DOCTYPE html>
<html>
<head>
<title>App Title</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div id="wrapper">
<header>
<% if yield(:header).empty? %>
<%= render 'header' %>
<% else %>
<%= yield :header %>
<% end %>
</header>
<article>
<%= yield %>
</article>
<footer>
<% if yield(:footer).empty? %>
<%= render 'footer' %>
<% else %>
<%= yield :footer %>
<% end %>
</footer>
</div>
</body>
</html>
<%= link_to image_tag('logo.png'), root_path %>
<%= link_to image_tag('logo.png'), admin_root_path %>
© <%= Date.today.year %> Company Name. All rights reserved.
<% content_for :header do %> <% end %>
<% content_for :footer do %> <% end %>
<% parent_layout 'application' %>