レヴュー: MasterView - Part 2 MasterViewの仕組み

Railsのテンプレートはレイアウト、コンテンツ、パーシャルに分類されます。ERBを使うとこれらは別のファイルになるのですが、なんとMasterViewでは一つのXHTMLファイルです。そしてそのファイルは、レンダリング結果のサンプルとしてブラウザで表示できます。さらに、そのファイルをWYSIWYGなHTMLエディタで編集しても、テンプレートとして壊れません。

MasterViewはどのようにしてそんな芸当を実現しているのでしょうか。MasterViewは独自のディレクティブをHTMLエレメントの属性として埋め込みます。例えば見出しを動的に生成するには次のように書きます。

<h1 mv:content="@title">Sample Title</h1>

上の見出しはそのままブラウザで表示すれば「Sample Title」ですが、レンダリングすると@titleの値が入るというわけです。

このようなディレクティブをXHTMLエレメントに加えることで、MasterViewはXHTMLファイルを複数のテンプレートに分割します。一般的には、最も外側の要素であるにレイアウトのテンプレート名を与えます。これは

<html mv:generate="layouts/foo.rhtml">
</html>

のように書きます(便宜上、他の属性は省いています)。このように書くと、<html></html>内の内容がlayouts/foo.rhtmlというテンプレートになります。

そして<html></html>の中に別のテンプレート名を指定された子孫要素があれば、その要素はレイアウトテンプレートから外され、新たなテンプレートとして認識されます(←ここがミソ)。

<html mv:generate="layouts/foo.rhtml">
  <head><title>タイトル</title></head>
  <body>
    <div mv:replace="@content_for_layout">
      <div mv:generate="foo/index.rhtml">
        メインコンテンツ
      </div>
    </div>
  </body>
</html>

(ちょっと端折りましたが、<div>mv:replace="@content_for_layout"</div><%= @content_for_layout %>になります。)上の内容をMasterViewでパースすると2つのテンプレートができるわけです。このパースは自動的に行われます。

パーシャルの切り出し方もほぼ同じです。ただしディレクティブにはmv:gen_partialを使います。

<html mv:generate="layouts/foo.rhtml">
  <head><title>タイトル</title></head>
  <body>
    <div mv:replace="@content_for_layout">
      <div mv:generate="foo/index.rhtml">
        メインコンテンツ
        <p mv:gen_partial=":partial => 'foo/message'">
          パーシャル
        </p>
      </div>
    </div>
  </body>
</html>

上のコードからはfoo/_message.rhtmlが生成されるというわけです。ここまでの内容はMasterViewのサイトで図で説明されています

テンプレートの再利用

作ったテンプレートは再利用しなければ意味がありません。ERBではテンプレートの再利用はレンダリング時にのみ発生しますが、MasterViewではテンプレートの段階で再利用できる必要があります。例えば一つのXHTMLファイルでレイアウトを定義したら、そのレイアウトを利用する別のファイルをブラウザで表示した時、指定したレイアウトで表示される必要があります。

そのためにMasterViewには、指定したテンプレートを別のXHTMLファイルにコピーするrakeのタスクが用意されています。コピーするレイアウトはmv:importで指定します。例えば上のレイアウトlayouts/foo.rhtmlfoo/show.rhtmlで再利用するには次のように書きます。

<html mv:import="layouts/foo.rhtml">
    <div mv:replace="@content_for_layout">
      <div mv:generate="foo/show.rhtml">
        showする
      </div>
    </div>
</html>

ユーザは<head>や<title>や<body>を手作業でコピーする必要はありません。上のようにコピーしたいテンプレートの名前だけをmv:importで指定して、rake mv:rebuild_allを実行すると、レイアウトがコピーされて下のようになります。

<html mv:import="layouts/foo.rhtml">
  <head><title>タイトル</title></head>
  <body>
    <div mv:replace="@content_for_layout">
      <div mv:generate="foo/show.rhtml">
        showする
      </div>
    </div>
  </body>
</html>

同様に、パーシャルを再利用するにはmv:import_renderを使います。

<html mv:import="layouts/foo.rhtml">
  <head><title>タイトル</title></head>
  <body>
    <div mv:replace="@content_for_layout">
      <div mv:generate="foo/show.rhtml">
        showする
        <p mv:import_render=":partial => 'foo/message'">
        </p>
      </div>
    </div>
  </body>
</html>

以上がMasterViewのキモとなる、テンプレートに関するディレクティブです。

このようにレンダリング前からテンプレートを再利用可能にするMasterViewのrakeですが、実際には致命的な欠陥があります。次回はその点を含め、 MasterViewの欠点を遠慮なく挙げていきます。