16
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SpeeeAdvent Calendar 2015

Day 18

Rails Viewでテンプレート継承する話

Last updated at Posted at 2015-12-18

この記事は Speee Advent Calendar の18日目です。

テンプレート継承とは

ページが表示する内容をブロック単位で定義して、ベースとなるテンプレートに その内容を継承する View の構築方法です。

言葉では全然頭に入ってこないので、以下の例をどうぞ。

Jade (Node) の例

テンプレート

top.jade
extends ./base.jade

block title
  title タイトル

block content
  h1 Hello, world!
  hr
  p This is template inheritance.
base.jade
doctype html
html
  head
    block title
      title untitled
  body
    block content

Twig (PHP) の例

テンプレート

top.html
{% extends "base.html" %}

{% block title %}タイトル{% endblock %}

{% block content %}
  <h1>Hello, world!</h1>
  <hr>
  <p>This is template inheritance.</p>
{% endblock %}
base.html
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}untitled{% endblock %} - テンプレート継承の話</title>
  </head>
  <body>
    <div id="content">{% block content %}{% endblock %}</div>
  </body>
</html>

出力 (整形したもの)

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <!-- title content -->
    <title>タイトル - テンプレート継承の話</title>
  </head>
  <body>
    <div id="content">
      <!-- block content -->
      <h1>Hello, world!</h1>
      <hr>
      <p>This is template inheritance.</p>
      <!-- /block content -->
    </div>
  </body>
</html>
  1. top.html に定義された block 内でテンプレートの内容を定義する。
  2. extend で指定されたベーステンプレートに継承する。
  3. ベーステンプレートで同名の block が呼び出され、継承元で定義された値が使われるようになる。
    • 継承元で定義されなかった場合は、ベーステンプレートの定義が使われる (例: title なら "untitled")

例に挙げた Jade や Twig など、割とモダンなテンプレートエンジンには実装されている傾向です。
あまり注目されませんが、実は Smarty なんかにも同様の実装 があります。

Rails でテンプレ継承 → content_for と友達になる

こういう書き方を Rails でやりたい時ってどうするんだ、って話ですが、content_for 系のメソッドを上手に活用することで実現できます(ActionView::Helpers::CaptureHelper にて定義されています)。

実はちゃんと Rails ガイドの最後の方で、その方法が言及されている のですが。
Slim で要点のみ整理すると、以下みたいな感じになります。

layouts/hello.slim
- content_for :stylesheets
  | #hello {
      display: none;
    }

- content_for :content
  h2 Hello content_for!
  p This is template inheritance.

= render template: 'layouts/application'
layouts/application.slim
doctype html
html
  head
    title Page title
    style
      = yield :stylesheets
  body
    #hello
      h1 hello
      hr
    = content_for?(:content) ? yield(:content) : yield

スクリーンショット 2015-12-18 15.40.37.png

表示としては、hello.slim で定義した CSS で div#hello が隠された上で、
content として指定したブロックの内容が表示されるようになります。

content_for :hoge <=> yield :hoge

content_for が既存テンプレートエンジンの block に相当する部分です。
Ruby block を渡すことで、ブロック内の出力に名前が付き、レンダリング終了まで保持されます。

定義されたブロックの内容を出力する場合は、 yield の引数に名前を指定することで出力できます。

content_for?

= content_for?(:content) ? yield(:content) : yield

ぱっと見ると、この行だけ謎めいていますが、大したことではなくて、:content という名前のブロックが定義されていれば、その内容を呼び出すというだけです。
もし無い場合は、普通の Rails layout と同じように、メインの出力内容を呼び出す 動きになります。

デフォルト値を使う

content_for? による判定を使えば、Jade や Twig のように、レイアウト側でデフォルト値を定義する事も可能です。

= content_for?(:content) ? yield(:content) : 'Default content'

応用: content_for で複数回定義

複数回のcontent_for
- content_for :metas
  = csrf_meta_tags

- content_for :metas
  = tag :meta, name: :robots, content: :noindex

- content_for :metas
  = tag :meta, name: 'twitter:card', content: :summary_large_image
  = tag :meta, name: 'twitter:domain', content: '...'

content_for は、同じ名前に対して複数回定義ができ、呼び出すたびに ブロックの後ろに内容が継ぎ足されて行きます
yield を行ったタイミングで全部一気に表示されるので、meta タグなんかで活用すると良さそうですね。参考...

もし内容を上書きしたい場合は、flush: true を指定しましょう。

上書き
- content_for :metas, flush: true
  /! 無

まとめ

content_for は良いヤツです。 仲間にして損は無し。

実際に テンプレート継承にするか、それとも partial render にするかは、どのようなページを構築するかに依るかと思いますので、適材適所で使いどころを見極めましょう。


おまけ: gem化

既存テンプレートエンジンのような機能(デフォルト値、append/prepend など)を扱うためには、View 側でやや実装が必要になるので、もっと手軽にできても良さそうです。

そんなこんなで gem の勉強も兼ね、Rails View にテンプレート継承機能を追加する Jubako gem を作ってみました。Layout とあわせて、テンプレート継承、多段継承を扱えます。
まだ生まれたてで、 テストすら無い ので、実践投入は控えていただきたいですが、検証等は歓迎です。

yhatt/jubako - Github

実装は Jade を意識しており、block append: :... block prepend: :... 等もあります。Slim と組み合わせる とそれっぽい感じ。

 

ちなみに仮名称は、Jade に語感を合わせた "Jedi" (ジェダイ) でした。 エピソード7は本日公開です。

 

16
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?