Amrita2でビューはさらにシンプルに!

Rails2.0環境に移行して、RESTなルート設定やオブジェクトによるURL指定、form_forの:builderオプションの仕組み等を試してきた。今はそのポリシーを改めて強く感じる。当たり前のことは書かない、同じことは繰り返さないと。現在のtest_slip202プロジェクトのコードは、start_form_tagしか知らなかった頃に比べ、相当なシンプルさだ。例えばnew、editアクションのコードの一部。

# app/views/slips/new.html.erb
<div class="slip" id="slip">
  <h1><%= _('New slip') %></h1>

  <%= render :partial=>'form', 
             :locals=>{:button_value=>_('Create')} %>

  <%= link_to _('Back'), slips_path %>
<div>
# app/views/slips/edit.html.erb
<div class="slip" id="slip">
  <h1><%= _('Editing slip') %></h1>

  <%= render :partial=>'form', 
             :locals=>{:button_value=>_('Update')} %>

  <%= link_to _('Show'), @slip %> |
  <%= link_to _('Back'), slips_path %>
</div>
# app/views/slips/_form.html.erb
<% slip_form_for @slip do |f| %>
  <%= f.text_field :number  %>
  <%= f.text_field :executed_on  %>
  <%= f.yen_field :total_yen  %>

  <%= f.error_messages_on :base %>
  <table class="journal" id="journal">
    <%= render :partial=>'journals/header' %>
    <%= render :partial=>'journals/form', :collection=>@journals %>
    <%= render :partial=>'journals/footer' %>
  </table>

  <%= f.submit button_value %>  
<% end %>

昨日までは、ビューでこれ以上簡潔にするのは無理だと思い込んでいるところがあった。(FormBuilderクラスやヘルパーにコードが分散してはいるが。)ところが最近、Amrita2というテンプレートエンジンの存在を知った。

そして、目に見える重複箇所や、当たり前の前提が未だあることに気付かされた...。

利用環境

インストール

  • http://rubyforge.org/projects/amrita2
    • 上記ページから最新版の2.0.1をダウンロードしてみた。(右側のダウンロードリンクをクリックして、amrita2-2.0.1.tar.gzをダウンロードした。)
    • もしかして、どこかにscript/plugin install可能なURLがあるのだろうか?
  • ダウンロードしたらダブルクリックで解凍。
  • OSXのFinder操作で、サンプルプロジェクトのtest_slip202/vendor/pluginsフォルダにコピーした。(ファイル名はamrita2に変更した。)

基本

  • Amrita2をインストールしても、今までのERbファイルは問題なく描画される。
  • Amrita2は、拡張子が「.a2」のファイルだけを、描画してくれるようだ。
  • もし、index.html.erbとindex.html.a2という拡張子が異なる同じファイル名が存在した場合は、「.a2」拡張子のAmrita2ファイルが優先される。
  • 「.a2」拡張子のAmrita2ファイルの中でもERb記法はある程度そのまま利用できる。

だから、最初からAmrita2のすべてを理解する必要はなく、便利だと思う機能から利用していけば良いのだ。そうすれば自分のような出来の悪いタイプでも、半日ほど集中して格闘して、その後はその学習コスト以上の恩恵を感じることができた。(基本的な文法や、ERbとの関わり方を理解しただけ、だが...。)

閉じるタグが不要になる
  • 最初に書いた重複箇所とは、HTMLの閉じるタグだ。いつも当たり前のように書いていたので忘れがちだが、思い返せばいつも煩わしさを感じていた。
  • Amrita2のAmXML記法を利用すれば、以下のように書ける。最初見たときは一瞬戸惑うが、慣れてきた今では快適。
# HTML
<div>
  <p>This is a test.</p>
</div>

# AmXML
<<div<
  <<p<
    This is a test.
ERbとの連携
  • ERbも混在できる
# AmXML
<<div<
  <<p<
    <%= Time.now %>
  • ERbが一行で完結するなら、以下のように書いてもOK。
# AmXML
<<div<
  <<p<
    %= Time.now
  • 複数行にまたぐ場合はエラーになる。
<<div<
  <<p<
    %= Time.now.strftime(
       "%Y-%m-%d")
# または...
<<div<
  <<p<
    %= Time.now.strftime(
    %= "%Y-%m-%d")
  • 複数行でも<%= %>を利用すればOK。
<<div<
  <<p<
    <%= Time.now.strftime(
        "%Y-%m-%d") %>
タグに属性を追加する
# HTML
<div>
  <p class="test">2008-02-10</p>
</div>

# AmXML
<<div<
  <<p class="test"<
    %= Time.now.strftime("%Y-%m-%d")
  • 属性がclassまたはidの時は、さらにシンプルに。上記は以下のようにも書ける。
# class="test"
<<div<
  <<p.test<
    %= Time.now.strftime("%Y-%m-%d")

# ちなみにid="test"の場合
<<div<
  <<p#test<
    %= Time.now.strftime("%Y-%m-%d")
インデントはタグの有効範囲を決める
  • 求められる結果
# AmXML
<<p<
  <<label for="slip_number" style="display:table"<
    伝票No.
  %= text_field :slip, :number

# HTML
<p>
  <label style="display: table;" for="slip_number"> 伝票No: </label>
  <input id="slip_number" type="text" size="30" name="slip[number]"/>
</p>
  • 伝票No.のインデントをしなかった場合
# AmXML
<<p<
  <<label for="slip_number" style="display:table"<
  伝票No.
  %= text_field :slip, :number

# HTML
<p>
  <label style="display: table;" for="slip_number"></label>
  伝票No.
  <input id="slip_number" type="text" size="30" name="slip[number]"/>
</p>
  • text_fieldをインデントをした場合
# AmXML
<<p<
  <<label for="slip_number" style="display:table"<
    伝票No.
    %= text_field :slip, :number

# HTML
<p>
  <label for = "slip_number" style = "display:table">
    伝票No:
    <input id="slip_number" name="slip[number]" size="30" type="text" />
  </label>
</p>
  • インデントは半角スペース1文字でも下がっていればインデントになる。
# AmXML
<<p<
 <<label for="slip_number" style="display:table"<
  伝票No
 %= text_field :slip, :number

# HTML
<p>
  <label for = "slip_number" style = "display:table">
    伝票No:
    <input id="slip_number" name="slip[number]" size="30" type="text" />
  </label>
</p>
  • 改行が入ってもインデントレベルには影響しない。
# AmXML
<<p<
  <<label for="slip_number" style="display:table"<
    伝票No.

  %= text_field :slip, :number

# HTML
<p>
  <label style="display: table;" for="slip_number"> 伝票No: </label>
  <input id="slip_number" type="text" size="30" name="slip[number]"/>
</p>
  • インデント記号はタブまたは半角スペースどちらでも良いが、タブまたは半角スペースの個数がインデントのレベル(深さ)になるようだ。
  • だからインデント記号にタブと半角スペースが混在してしまうと混乱する。(タブ設定が半角1文字分なら問題ないが。)
  • 間違っても全角スペースは使わないこと。(描画が乱れて悩んでしまった...。)

素朴な疑問

  • 実はここまでindex.html.a2ファイルに追記したテストで、その後にh1タグが続くのだが、HTMLソースを確認すると以下のようになっていた。
# AmXML
<<div<
  <<p.test<
    %= Time.now.strftime("%Y-%m-%d")

<<h1<
  %= _('Listing slips')


# 確認したHTMLソース
<div><p class = "test">2008-02-10
</p></div><h1>  伝票リスト
</h1>
  • ブラウザの描画結果はまったく同じなのだが、やはりHTMLソースも改行されて欲しい...。
  • 属性値を指定する = 前後のスペースが気になる...。
  • 自分が思い描くHTMLソースは以下。
<div>
  <p class="test">
    2008-02-10
  </p>
</div>

<h1>
  伝票リスト
</h1>
  • どこかで何かを設定をすれば、コントロールできるのだろうか?
      • CommandFilterを利用して、シェルコマンドからtidyを実行すれば整形されたHTMLが出力された!

インスタンス変数と連動させる

  • 以下はindex.html.erbの一部を抜粋してみた。リスト表示の典型的なコードスタイルだ。
<table>
<% for slip in @slips %>
  <tr>
    <td align="left" ><%=h slip.number %></td>
    <td align="left" ><%=h slip.executed_on %></td>
    <td align="right"><%=h number_with_delimiter(slip.total_yen) %></td>
    <td><%= link_to _('Show'), slip %></td>
    <td><%= link_to _('Edit'), edit_slip_path(slip) %></td>
    <td><%= link_to _('Destroy'), slip, :confirm => _('Are you sure?'), :method => :delete %></td>
  </tr>
<% end %>
</table>
  • 上記をAmrita2を利用すると、以下のように書くことができた。
<<table<
  <<tr :slips <
    <<td align="left" :number >>
    <<td align="left" :executed_on >>
    <<td align="right" :total_yen >>
    <<td.action_link<
      %= link_to _('Show'), $_
    <<td.action_link<
      %= link_to _('Edit'), edit_slip_path($_)
    <<td.action_link<
      %= link_to _('Destroy'), $_, :confirm=>_('Are you sure?'), :method=>:delete

素晴らしいシンプルさだ!(配列は繰り返し表示するというルールにして、forループや:collection指定さえも省略してしまうなんて。)

  • forループはどこへ行った?という感じだが、2行目の「<<tr :slips <」に注目。
    • :slipsはインスタンス変数@slipsを示していて、その内容が配列であれば、trタグの範囲を勝手に繰り返してくれる。(render :partial=>'XXXX', :collection=>@slipsな感覚)
  • そしてその下の3行目は「<<td align="left" :number >>」となっている。
    • :numberは配列中の個々のモデルインスタンスから:number属性を取り出してくれる。
    • もちろんエスケープ済み。(<%=h slip.number %>と同じ結果)
  • それではループ中に個々のモデルインスタンスを指定する場合はどうするのか?
    • グローバル変数「$_」に代入されるようだ。
    • link_toで利用していたローカル変数slipは$_に置き換えることで問題なく動作した。
  • もし、コード行数をさらに減らすことを優先すれば、HTMLやERb記法もそのまま使えるので、以下のように書いてもOK。
<<table<
  <<tr :slips <
    <<td align="left" :number >>
    <<td align="left" :executed_on >>
    <<td align="right" :total_yen >>
    <td class="action_link"><%= link_to _('Show'), $_ %></td>
    <td class="action_link"><%= link_to _('Edit'), edit_slip_path($_) %></td>
    <td class="action_link"><%= link_to _('Destroy'), $_, :confirm => _('Are you sure?'), :method => :delete %></td>
  • さらに、tdタグの別の記法を利用すると、以下のように書き換えることも出来る。
      • もし自動改行される場合は、感動するために是非、ブラウザの文字サイズを小さくしてみて欲しいです。(自動改行を抑えて、横スクロールモードにするスタイル設定が知りたい...。)
<<table<
  <<tr :slips<---------------------------------------------------------------------------------------------------------------------------------
    |align||left       |left            |right         |                       |                           |                                    |
    |class||           |                |              |action_link            |action_link                |action_link                         |
    |     ||<<:number>>|<<:executed_on>>|<<:total_yen>>|<%= link_to _('Show'), |<%= link_to _('Edit'),     |<%= link_to _('Destroy'),           |
    |     ||           |                |              |    $_ %>              |      edit_slip_path($_) %>|    $_,                             |
    |     ||           |                |              |                       |                           |    :confirm => _('Are you sure?'), |
    |     ||           |                |              |                       |                           |    :method => :delete %>           |
    ---------------------------------------------------------------------------------------------------------------------------------------------

こんな書式が許されるなんて...。驚きです。

  • 「|」で区切った範囲が、tdタグの1セルと解釈されるようだ。
  • 「||」で区切った左側で属性を指定すると...
    • その行はtdタグの属性値の指定になる。
    • 属性値の指定が無ければ、半角スペースで埋めておけば良い。
  • 上下のハイフンの連続は、無くてもOK。
  • 「|」の間隔も最低1スペースあればOK。(インデントを揃えておかないと、この書式の意味は無いが。)
  • ちなみに、「||」の右側の「|」を「||」に置き換えるとthタグと解釈された。
    • td、th以外の目的で「|」を利用するときは、エスケープ「\|」しておく必要があるようだ。
<<table<
  <<tr<----------------------------------------------------------------------------------------------
    | ||<%= _("Slip\|Number") %>||<%= _("Slip\|Executed on") %>||<%= _("Slip\|Total yen") %>|| || || ||
    ---------------------------------------------------------------------------------------------------

実は勉強不足

ここまで、自分が理解できていることの説明だが、おそらくAmritaの機能の半分も活用していないと思う。(作者さんが想定する本来の使い方ではないかもしれない...。)しかし、自分にとってはこれだけでも十分幸せなビュー環境だ。
さらにフィルターやマクロといった機能を利用すれば、もっともっと素晴らしいことが実現できそう。(詳細はgem戦記さん)そのうち、Amrita2でビューは究極のシンプルさになる、と言えるようになりたい。