Application Layoutを整理する3つの方法


サービスを運営していると、

* Admin以下のページだけちょっとヘッダーを変えたい
* ABテストのために2種類のレイアウトを試してみたい
* キャンペーン用にある期間だけ別のロゴを使いたい
* ユーザー登録ページだけ簡易レイアウトにしたい

などなどの理由で、`app/views/layouts/application.html.erb`と大体同じだけど、ちょっと違うというレイアウトが欲しくなります。ただ、コピペでいろんなレイアウトを作ってしまうと後の変更が困難になります。Layoutでもデフォルト値や継承を使って再利用性の高いものを作りましょう。

## 方法1 Default content_for

あるyieldに対応するcontent_forがセットされなかった時のデフォルト値を定める方法です。

yieldは普通の文字列を返す関数で、値がセットされていなければ`""`が返ります。なので`empty?`を使って値がセットされているか調べることができます。

```ruby:app/views/layouts/application.html.erb
<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だとすると、以下のようになります。

```ruby:app/views/layouts/application.html.erb
<%= render 'header' %>
```

```ruby:app/views/application/_header.html.erb
<div class="head">Normal Page</div>
```

ここでproducts以下のページだけheader部分の内容を変えたいとしたら、以下のように`products`ディレクトリの下に同名の`_header.html.erb`を作り内容を上書きます。

```ruby:app/views/products/_header.html.erb
<div class="head">Production Page</div>
```

お分かりかと思いますが、これは、以下のようにProductsControllerがApplicationControllerを継承している事を利用しています。

```ruby:app/controllers/products_controller.rb
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/ を参照して直したのが以下です。

```ruby:app/helpers/application_helper.rb
module ApplicationHelper
  def parent_layout(layout)
    @view_flow.append(:layout, self.output_buffer)
    self.output_buffer = render(file: "layouts/#{layout}")
  end
end
```

これを使って、

```ruby:app/views/layouts/admin.html.erb
<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つを併用した使用例

これらの方法はどれかを選ぶというよりかは併用すると良いです。

```ruby:app/views/layouts/application.html.erb
<!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>
```

```ruby:app/views/application/_header.html.erb
<%= link_to image_tag('logo.png'), root_path %>
```

```ruby:app/views/admin/base/_header.html.erb
<%= link_to image_tag('logo.png'), admin_root_path %>
```

```ruby:app/views/application/_footer.html.erb
&copy; <%= Date.today.year %> Company Name. All rights reserved.
```

```ruby:app/layout/no_header_footer.html.erb
<% content_for :header do %> <% end %>
<% content_for :footer do %> <% end %>
<% parent_layout 'application' %>
```