Haml を実際に Rails で使うチュートリアル

第四十三回

先日のエントリ「Ruby on Rails の View を気持ちよくシンプルに書ける Haml - のほほん徒然」で紹介したHamlを実際にRailsのアプリケーションで使用してみました.
私が大学院の講義で出されたレポート課題「Web Service*1を利用したWeb Applicationを作成しなさい」の成果物として昨年作った「http://www.unraki.com/imozuru/items」を利用してみます.作成時に書いたViewはrhtmlでしたので,それをHamlで書き直してみるというチュートリアルにします.

Haml Pluginをインストール

すでに,Railsプロジェクトの作成や,データベースとの連携など初期設定は済んでいる前提とします.なお,今回は既に作成済みのrhtmlをHamlで書き直しますが,プロジェクト開始時からViewはHamlで書こう!と決めている場合も作業はほぼ同じです.
まず,#{RAILS_ROOT}へ移動してプラグインをインストールします.

$ cd #{RAILS_ROOT}
$ ruby script/plugin install http://svn.hamptoncatlin.com/haml/trunk
$ mv  vendor/plugin/trunk vendor/plugin/haml-1.4.0

続いて,イキナリですがプラグインの修正をします(笑)この記事を書いている時点で,インストールしたHaml-1.4.0にはTypoが一箇所存在するためです.適当なエディタで以下のファイルを修正します.

#{RAILS_ROOT}/vendor/plugin/haml-1.4.0/lib/haml/helper.rb

l.243 - l.249

module ActionView
  class Base # :nodoc:
    def is_haml?
      false
    end # <- この行の'end'が抜けているので追加する
  end
end

HamlによるViewファイルを作成する

今回のケースでは,アプリケーション全体で利用するViewであるapp/view/layouts/items.rhtmlを書き換えます.

app/view/layouts/items.rhtml
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Amazon書籍イモヅル検索 <%= "#{@q_words.join(' ')}」検索結果" unless @q_words.blank? %></title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <meta http-equiv="Content-Script-Type" content="text/javascript" />
  <meta http-equiv="Content-Style-Type" content="text/css" />
  <%= stylesheet_link_tag "scaffold" %>
  <%= stylesheet_link_tag "thickbox" %>
  <%= javascript_include_tag :defaults %>
  <%= javascript_include_tag "jquery" %>
  <%= javascript_include_tag "thickbox" %>
</head>
<body>
<div class="center">
  <div id="header">
    <div id="logo">
      <%= link_to_unless_current image_tag("logo_ImozuruSearch.jpg", :alt=> "One After Another"), :action => "index" %>
    </div>
    <p class="notice"><%= flash[:notice] %></p>
    <%= form_tag({:action => "search"}, :class => "query") %>
      <%= text_field_tag "q", @q_words ? @q_words.join(" ") : "google web", :size => 40 %>
      <%= submit_tag " 検索 " %>
      <%= link_to_function image_tag("question-mark.gif"), "$('#about').toggle()" %>
      <span id="loading_icon" style="display: none;"><%= image_tag "indicator.gif", :alt => "loading.." %></span>
    <%= end_form_tag %>
    <div id="about" style="display: none;">
      <dl>
        <dd>これはなに?</dd>
        <dt>
          <p>検索ボックスに入力された単語に,<%= link_to "Yahoo!JAPAN", "http://www.yahoo.co.jp/", :title => "Yahoo" %>による関連語を付けて,
            <%= link_to "Amazon", "http://www.amazon.co.jp/", :title => "Amazon" %>から関係のありそうな本をイモヅル式にひっぱる検索エンジンです.
          </p>
          <p>気になる本を探しながら,関連するものも一緒に見られます.</p>
        </dt>
        <dd>使い方</dd>
        <dt>
          <p>気になる言葉を検索ボックスに空白で区切って入力してください.</p>
          <p>ダブルクォーテーション(&quot;)で複数の単語を囲めばフレーズ検索もできます.</p>
        </dt>
      </dl>
    </div>
  </div>

  <%= yield %>

  <div id="footer">
    <p>
      Web Services by <%= link_to "Yahoo!JAPAN", "http://developer.yahoo.co.jp/", :title => "Yahoo!デベロッパーネットワーク" %>,
      <%= link_to "Amazon", "http://aws.amazon.com/", :title => "Amazon Web Services" %>
    </p>
  </div>
</div>
  <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
  </script>
  <script type="text/javascript">
    _uacct = "UA-294132-3";
    urchinTracker();
  </script>
</body>

われながらとてつもなく見づらいソースですが,これをHamlで書き直すためにまず名前を変更します.といっても拡張子を変えるだけですが.

$ mv app/view/layouts/items.rhtml app/view/layouts/items.haml

そして,書き直したHamlファイルの内容は以下のようになります.

app/view/layouts/items.haml
!!! XML
!!!
%html
  %head
    %title
      Amazon書籍イモヅル検索
      = "「#{@q_words.join(' ')}」検索結果" unless @q_words.blank?
    %meta{ 'http-equiv' => 'Content-Type', :content => 'text/html; charset=UTF-8'}
    %meta{ 'http-equiv' => 'Content-Script-Type', :content => 'text/javascript'}
    %meta{ 'http-equiv' => 'Content-Style-Type', :content => 'text/css'}
    = stylesheet_link_tag 'scaffold'
    = stylesheet_link_tag 'thickbox'
    = javascript_include_tag :defaults
    = javascript_include_tag 'jquery'
    = javascript_include_tag 'thickbox'
  %body
    .center
      #header
        #logo
          = link_to_unless_current image_tag('logo_ImozuruSearch.jpg', :alt=> 'One After An\
other'), :action => 'index'
        %p.notice
          = flash[:notice]
        %form.query{ 'action' => url_for( :action => 'search' ) }
          = text_field_tag 'q', @q_words ? @q_words.join(' ') : 'google web', :size => 40
          = submit_tag ' 検索 '
          = link_to_function image_tag('question-mark.gif'), "$('#about').toggle()", :title => 'Toggle Help Block'
        #about{ :style => 'display: none;' }
          %dl
            %dd これはなに?
            %dl
              %p
                検索ボックスに入力された単語に,
                = link_to "Yahoo!JAPAN", "http://www.yahoo.co.jp/", :title => "Yahoo"
                による関連語を付けて,
                = link_to "Amazon", "http://www.amazon.co.jp/", :title => "Amazon"
                から関係のありそうな本をイモヅル式にひっぱる検索エンジンです.
              %p 気になる本を探しながら,関連するものも一緒に見られます.
            %dd 使い方
            %dl
              %p 気になる言葉を検索ボックスに空白で区切って入力してください.
              %p ダブルクォーテーション(&quot;)で複数の単語を囲めばフレーズ検索もできます.
      = yield
      #footer
        %p Web Services by
        = link_to "Yahoo!JAPAN", "http://developer.yahoo.co.jp/", :title => "Yahoo!デベロッパーネットワーク"
        ,
        = link_to "Amazon", "http://aws.amazon.com/", :title => "Amazon Web Services"
    %script{ 'type' => 'text/javascript', :src => "http://www.google-analytics.com/urchin.js" }
    %script{ 'type' => 'text/javascript' }
      _uacct = "UA-294132-3";
      urchinTracker();

とこんな感じになります.このとき,同じ名前のrhtmlファイルが存在する場合,Hamlファイルが優先されます*2.この場合だと,items.rhtmlとitems.hamlが両方存在する場合,items.hamlが処理されます.

出力されるHTML

以上のように書き換え,ブラウザで確認すると以下のようなHTMLに変換されます.

<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <title>
      Amazon書籍イモヅル検索
    </title>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
    <meta content='text/javascript' http-equiv='Content-Script-Type' />
    <meta content='text/css' http-equiv='Content-Style-Type' />
    <link href="./stylesheets/scaffold.css?1172664322" media="screen" rel="Stylesheet" type="text/css" />
    <link href="./stylesheets/thickbox.css?1163099273" media="screen" rel="Stylesheet" type="text/css" />

    <script src="./javascripts/prototype.js?1163084730" type="text/javascript"></script>
    <script src="./javascripts/effects.js?1163084730" type="text/javascript"></script>
    <script src="./javascripts/dragdrop.js?1163084730" type="text/javascript"></script>
    <script src="./javascripts/controls.js?1163084730" type="text/javascript"></script>
    <script src="./javascripts/application.js?1163084730" type="text/javascript"></script>
    <script src="./javascripts/jquery.js?1163066375" type="text/javascript"></script>

    <script src="./javascripts/thickbox.js?1172640307" type="text/javascript"></script>
  </head>
  <body>
    <div class='center'>
      <div id='header'>
        <div id='logo'>
          <img alt="One After Another" src="./images/logo_ImozuruSearch.jpg?1172642737" />
        </div>

        <p class='notice'>
        </p>
        <form action='./items/search' class='query'>
          <input id="q" name="q" size="40" type="text" value="google web" />
          <input name="commit" type="submit" value=" 検索 " />
          <a href="#" onclick="$('#about').toggle(); return false;" title="Toggle Help Block"><img alt="Question-mark" src="./images/question-mark.gif?1127313754" /></a>
        </form>
        <div id='about' style='display: none;'>
          <dl>

            <dd>これはなに?</dd>
            <dl>
              <p>
                検索ボックスに入力された単語に,
                <a href="http://www.yahoo.co.jp/" title="Yahoo">Yahoo!JAPAN</a>
                による関連語を付けて,
                <a href="http://www.amazon.co.jp/" title="Amazon">Amazon</a>
                から関係のありそうな本をイモヅル式にひっぱる検索エンジンです.
              </p>

              <p>
                気になる本を探しながら,関連するものも一緒に見られます.
              </p>
            </dl>
            <dd>使い方</dd>
            <dl>
              <p>
                気になる言葉を検索ボックスに空白で区切って入力してください.
              </p>

              <p>
                ダブルクォーテーション(&quot;)で複数の単語を囲めばフレーズ検索もできます.
              </p>
            </dl>
          </dl>
        </div>
      </div>

      :
      :
   (中略)
      :
      :

      <div id='footer'>
        <p>Web Services by</p>
        <a href="http://developer.yahoo.co.jp/" title="Yahoo!デベロッパーネットワーク">Yahoo!JAPAN</a>
        ,
        <a href="http://aws.amazon.com/" title="Amazon Web Services">Amazon</a>
      </div>
    </div>
    <script src='http://www.google-analytics.com/urchin.js' type='text/javascript'>
    </script>

    <script type='text/javascript'>
      _uacct = "UA-294132-3";
      urchinTracker();
    </script>
  </body>
</html>

と,こんな感じで出力されます.

便利そうだが,あくまでプログラマ視点のような気がする.

実際のWebシステム開発を考えると,デザイナの理解できる(しやすい)ERB等が使われそう.

id:Hexaさんのおっしゃるように,元のrhtmlファイルに比べてHamlで書いたViewは,エンジニアにとってみると短く,シンプルで,見やすいものになっていると思います.しかし,このViewをデザイナさんに渡してデザインしてもらうというのは難しいのかもしれません.なぜなら,色々なところを省略しすぎて書いた本人しか良く分からないから.なにかうまい解決方法はないものでしょうか.

まとめ

今回はHamlRailsで利用する際の具体的なチュートリアルを,http://www.unraki.com/imozuru/itemsを題材に紹介しました.大まかな利用手順をまとめると以下のようになります.

  1. Railsプロジェクト開始
  2. Hamlをインストール(必要に応じてTypo修正)
  3. rhtmlファイルが既にある場合は名前を変える
  4. Hamlで書く
エラーへの対処法

今回,いくつかHamlのパースエラーに遭遇しましたので,対処法を残しておきます.

Application Errorと表示されたら
ログを見ましょう.Hamlのパースエラーはブラウザで確認することができません.そのため,#{RAILS_ROOT}/log/#{RAILS_ENV}.logにエラー内容が残ります.
ログにTemplateErrorと書いてあったら
ほとんどがインデントの深さと,改行に関することが原因です.まずはHTMLになったときのDOM構造をイメージしてインデントの深さを適切に調整してください.また,次のような書き方は私の確認したところでは無理なようです.
  #logo
    = link_to_unless_current image_tag('logo_ImozuruSearch.jpg',
                                        :alt=> 'One After Another'),
                             :action => 'index'

Rubyでは区切りの良いところならば,メソッドへの引数などは複数行にわたって書いても大丈夫なのですが,Hamlの場合これはダメなようです.正しくは以下のように1行で書きます.

  #logo
    = link_to_unless_current image_tag('logo_ImozuruSearch.jpg', :alt=> 'One After Another'), :action => 'index'

*1:広義の

*2:Hamlプラグインをインストールしたためです