デイブトーマス本のサンプルをAmrita2化してみた

Agile Web Development With Rails: A Pragmatic Guide (The Facets Of Ruby Series)
Agile Web Development With Rails: A Pragmatic Guide (The Facets Of Ruby Series)

これのチュートリアル部分で使われている depot というアプリを、開発中のAmrita2最新版で書いてみました。

storeコントローラーのViewだけ rhtml版と対比して示します。View以外はほぼ無修正で、a2html版(Amrita2で作成したビュー)とrhtml版は同じ結果を表示します。

それ以外のソースは、こちらのサポートページにあります。

(index.rhtml)

<h1>Your Pragmatic Catalog</h1>

<% for product in @products -%>
  <div class="entry">
    <%= image_tag(product.image_url) %>
    <h3><%= h(product.title) %></h3>
    <%= product.description %>
    <div class="price-line">
      <span class="price"><%= number_to_currency(product.price) %></span>

      <% form_remote_tag :url => { :action => :add_to_cart, :id => product } do %>
        <%= submit_tag "Add to Cart" %>
      <% end %>

      </div>
  </div>
<% end %>

(index.a2html)

<h1>Your Pragmatic Catalog</h1>

  <<div.entry :products <
    %= image_tag($_.image_url)
    <<h3:title>>
    <<:description>>
    <<div.price-line<
      <<span.price :price | :to_currency  >>
      %  form_remote_tag :url => { :action => :add_to_cart, :id => $_ } do
      %=   submit_tag "Add to Cart"
      %  end


(_cart.rhtml)

<div class="cart-title">Your Cart</div>

<table>
  <%= render(:partial => "cart_item", :collection => cart.items) %>
  <tr class="total-line">
    <td colspan="2">Total</td>
    <td class="total-cell"><%= number_to_currency(cart.total_price) %></td>
  </tr>
</table>

<%= button_to "Checkout", :action => :checkout %>
<%= button_to "Empty cart", :action => :empty_cart %>

(_cart.a2html)

<<div.cart-title<
  Your Cart

<<table:cart<
  %= render(:partial => "cart_item", :collection => $_.items)
  <<tr class="total-line"<-------------------------------------
    | colspan  ||     2    |                                  |
    | class    ||          | total-cell                       |
    |          ||  Total   | %= $_.total_price.to_currency    |
    -----------------------------------------------------------

%= button_to "Checkout", :action => :checkout
%= button_to "Empty cart", :action => :empty_cart

(_cart_item.rhtml)

<% if cart_item == @current_item %>
  <tr id="current_item">
<% else %>
  <tr>
<% end %>
  <td><%= cart_item.quantity %> X </td>
  <td><%= h(cart_item.title) %></td>
  <td class="item-price"><%= number_to_currency(cart_item.price) %></td>
</tr>


(_cart_item.a2html)

<< :cart_item {
    :current => ($_ == @current_item),
    :price => $_.price.to_currency
   } <
  <<tr id="current_item" : | Attr[:id=>:current]
    <------------------------------------------------
    |class||                |          | item-price |
    |     ||<<:quantity>> X |<<:title>>| <<:price>> |
    -------------------------------------------------

ここで使っている to_currency というメソッドは、次のように定義してあります。

module PriceHelper
  include ActionView::Helpers::NumberHelper
  def to_currency
    number_to_currency(self)
  end
end

class BigDecimal
  include PriceHelper
end

class Integer
  include PriceHelper
end


(checkout.rhtml)

<div class="depot-form">

  <%= error_messages_for 'order' %>

  <fieldset>
    <legend>Please Enter Your Details</legend>

    <% form_for :order, :url => { :action => :save_order } do |form| %>
      <p>
        <label for="order_name">Name:</label>
        <%= form.text_field :name, :size => 40 %>
      </p>

      <p>
        <label for="order_address">Address:</label>
        <%= form.text_area :address, :rows => 3, :cols => 40 %>
      </p>

      <p>
        <label for="order_email">E-Mail:</label>
        <%= form.text_field :email, :size => 40 %>
      </p>

      <p>
        <label for="order_pay_type">Pay with:</label>
        <%=
          form.select :pay_type,
                       Order::PAYMENT_TYPES,
                      :prompt => "Select a payment method"
        %>
      </p>

      <%= submit_tag "Place Order", :class => "submit" %>
    <% end %>
  </fieldset>
</div>

(checkout.a2html)

<%(BeforeCompile)
  use_macro(StandardForm)
%>

<div class="depot-form">

  %= error_messages_for 'order'

  << standard_form record="order" <
    <form_title>Please Enter Your Details</form_title>
    <url action = "save_order" />
    <<header<
      %  pay_select = select "order", :pay_type,
      %               Order::PAYMENT_TYPES, :prompt => "Select a payment method"
    <<<--------------------------------------------------------------
      ||| Name     || <text id="name" size="40" />                  |
    <<<--------------------------------------------------------------
      ||| Address  || <text_area id="address" rows="3" cols="40" /> |
    <<<--------------------------------------------------------------
      ||| E-Mail   || <text id="email" size="40" />                 |
    <<<--------------------------------------------------------------
      ||| Pay with || <<field id="pay_type"<                        |
      |||          ||   <<:pay_select\|NoSanitize>>                 |
      ---------------------------------------------------------------
    <<fotter<
      %= submit_tag "Place Order", :class => "submit"

このビューでは、standarad-form というAmrita2のマクロを定義して、使用しています。

standard-formのソースは次のとおりです。

(helper/standard_form.rb)

class StandardForm < Amrita2::Macro::Base
  TemplateText = <<-END_OF_TEMPLATE
    <<fieldset<
      <<legend:title>>
      <%%= form_tag(<%= $_[:url].inspect %>) %%>
        <<:header>>
        <<p:items<
          <<label:label|Attr[:for]<
            <<:text>>:
          <<:input>>
        <<:fotter>>
      <%%= '</form>' %%>

  END_OF_TEMPLATE

  def macro_data(element)
    root = element.as_amrita_dictionary
    record = root[:record].intern
    title = element.search("form_title").first
    url = element.search("url").first
    header = element.search("header").first
    items = element.search("tr").collect do |tr|
      tr_to_data(record, tr)
    end
    fotter = element.search("fotter").first

    ret = {
      :title => title.contents,
      :record => record,
      :url => url ? url.as_amrita_dictionary : {},
      :header => header.contents,
      :items => items,
      :fotter => fotter.contents
    }
    ret
  end

  def tr_to_data(record, tr)
    label = tr.search("th").first
    td = tr.search("td").first
    input = td_to_input(record, td) || { }
    {
      :label => {
        :for => "#{record}_#{input[:field_id]}",
        :text => label.contents.strip,
      },
      :input => input[:input]
    }
  end

  def td_to_input(record, td)
    x = td.children.find { |c| c.kind_of?(Hpricot::Elem) }
    raise "input was not found in <td>" unless x
    attr = x.as_amrita_dictionary
    field_id = attr.delete(:id)
    input =  case x.name
             when "text"
               "<%= text_field #{record.to_s.inspect}, #{field_id.to_s.inspect}, #{attr.inspect} %>"
             when "text_area"
               "<%= text_area #{record.to_s.inspect}, #{field_id.to_s.inspect}, #{attr.inspect} %>"
             when "field"
               x.contents
             else
               raise "unknown input field #{x.name} "
             end
    {
      :input => Amrita2::SanitizedString[input],
      :field_id => field_id
    }
  end
end


(layout/store.rhtml)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--
 ! Excerpted from "Agile Web Development with Rails, 2nd Ed."
 ! We make no guarantees that this code is fit for any purpose.
 ! Visit http://www.pragmaticprogrammer.com/titles/rails2 for more book information.
-->
<html>
<head>
  <title>Pragprog Books Online Store</title>

  <%= stylesheet_link_tag "depot", :media => "all" %>

  <%= javascript_include_tag :defaults %>
</head>

<body id="store">
  <div id="banner">
    <%= image_tag("logo.png") %>
    <%= @page_title || "Pragmatic Bookshelf" %>
  </div>
  <div id="columns">
    <div id="side">


    <% hidden_div_if(@cart.items.empty?, :id => "cart") do %>
      <%= render(:partial => "cart", :object => @cart) %>
    <% end %>

      <a href="http://www....">Home</a><br />
      <a href="http://www..../faq">Questions</a><br />
      <a href="http://www..../news">News</a><br />
      <a href="http://www..../contact">Contact</a><br />
    </div>
    <div id="main">
    <% if flash[:notice] -%>
      <div id="notice"><%= flash[:notice] %></div>
    <% end -%>
      <%= yield :layout %>
    </div>
  </div>
</body>
</html>

(layout/store.rhtml)

<%(BeforeCompile) opt[:process_xhtml]=true %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--
 ! Excerpted from "Agile Web Development with Rails, 2nd Ed."
 ! We make no guarantees that this code is fit for any purpose.
 ! Visit http://www.pragmaticprogrammer.com/titles/rails2 for more book information.
-->
<<html<
  <<head<
    <title>Pragprog Books Online Store</title>

    %= stylesheet_link_tag "depot", :media => "all"
    %= javascript_include_tag :defaults

  <<body#store<
    <<div#banner<
      %= image_tag("logo.png")
      %= @page_title || "Pragmatic Bookshelf"

    <<div#columns<
      <<div#side<
        % style = @cart.items.empty? ? "display: none" : false
        <<div#cart : | Attr[:style] <
          %= render(:partial => "cart", :object => @cart)

        <a href="http://www....">Home</a><br />
        <a href="http://www..../faq">Questions</a><br />
        <a href="http://www..../news">News</a><br />
        <a href="http://www..../contact">Contact</a><br />

      <<div#main<
        <<div#notice ?[flash[:notice]]<
          %= flash[:notice]

        << :content_for_layout | NoSanitize >>