Rails2.2でサンプルアプリを作ってみる(6)

Rails2.2を利用したサンプルアプリ作成の続きです。

すでにここまで作成が終わっているものとして話を進めていきます。

  1. http://d.hatena.ne.jp/solitary_shell/20081215/1229294453
  2. http://d.hatena.ne.jp/solitary_shell/20081216/1229359675
  3. Rails2.2でサンプルアプリを作ってみる(3) - 積み重ねた日々
  4. http://d.hatena.ne.jp/solitary_shell/20081218/1229527895
  5. Rails2.2でサンプルアプリを作ってみる(5) - 積み重ねた日々

今回の作業内容

今回は、以下の変更を行っていきたいと思います。

  • エラー処理を追加する
  • カートを空にする処理を追加する

エラー処理を追加する

現状のアプリケーションのままでは、カートに入れる商品が見つからない場合には、ActiveRecordがRecordNotFoundを投げるために、この例外を処理する必要があります。

http://localhost:3000/store/add_to_cart/foo へアクセスすると以下のエラー画面が表示されます。

このままでは、ユーザに対してエラー画面を表示することになるので、以下の対応を行うことにします。

  • エラーが発生してことをログファイルに残す
  • 「無効な商品です」と短いメッセージを出力する
  • 商品カタログページを再表示する

Controller(app/controllers/store_controller.rb)のadd_to_cartメソッド内にそれらの処理を記述します。

  def add_to_cart
    begin
      product = Product.find(params[:id])
    # 商品が見つからなかった場合の例外処理
    rescue ActiveRecord::RecordNotFound
      # エラー内容をログに残す
      logger.error('Attempt to access invalid product #{params[:id]}')
      # ユーザにエラーメッセージを表示する
      flash[:notice] = 'Invalid product'
      # 商品カタログページへリダイレクト
      redirect_to :action => :index
    else
      @cart = find_cart
      @cart.add_product(product)
    end
  end

http://localhost:3000/store/add_to_cart/foo へもう一度アクセスしてみます。今度は、さっきのエラー画面が表示されなくなり、代わりに商品カタログページが再表示するようになりました。

またエラーメッセージがログに保存されることを確認します。

$ tail -f log/development.log
Processing StoreController#add_to_cart (for 127.0.0.1 at 2008-12-23 21:48:31) [GET]
  Session ID: c32cc15750e86c1295f93e296cd8b2da
  Parameters: {"id"=>"foo"}
  Product Columns (1.7ms)   SHOW FIELDS FROM `products`
  Product Load (0.4ms)   SELECT * FROM `products` WHERE (`products`.`id` = 0) 
Attempt to access invalid product foo
Redirected to actionindex
Completed in 7ms (DB: 3) | 302 Found [http://localhost/store/add_to_cart/foo]


Processing StoreController#index (for 127.0.0.1 at 2008-12-23 21:48:31) [GET]
  Session ID: c32cc15750e86c1295f93e296cd8b2da
  Product Load (4.3ms)   SELECT * FROM `products` ORDER BY title
Rendering template within layouts/store
Rendering store/index
  Product Columns (24.3ms)   SHOW FIELDS FROM `products`
Completed in 59ms (View: 32, DB: 7) | 200 OK [http://localhost/store]

しかし、さきほど記述したユーザに対するエラーメッセージが表示されていません。テンプレートに記述していないので、当たり前ですね。ということで、flashを表示するブロック要素を追加します。

      <% if flash[:notice] -%>
        <div id="notice"><%= flash[:notice] %></div>
      <% end -%>

これらは、商品カタログ全体で利用したいので、app/views/layouts/store.html.erb に追加します。最終的には、以下のようになりました。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
  <title>Pragprog Books Online Store</title>
  <%= stylesheet_link_tag "depot", :media => "all" %>
</head>
<body id="store">
  <div id="banner">
    <%= image_tag("logo.png") %>
    <%= @page_title || "Pragmatic Bookshelf" %>
  </div>
  <div id="columns">
    <div id="side">
      <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>

もう一度、http://localhost:3000/store/add_to_cart/foo へアクセスします。今度は、エラーメッセージも正しく表示されるようになりました。


カートを空にする処理を追加する

今までの作業の流れに従って、カートを空にする処理を追加していきます。

  1. テンプレートにカート内に商品を空にするボタンを追加する
  2. カート内の商品を空にするアクションをコントローラに追加する

app/views/store/add_to_cart.html.erb に、商品を空にするボタンを追加します。

<%= button_to 'Empty cart', :action => :empty_cart %>

次に app/controllers/store_controller.rb に、empty_cartメソッドを追加します。

  def empty_cart
    session[:cart] = nil
    flash[:notice] = 'Your cart is currently empty'
    redirect_to :action => :index
  end

一通り実装したので、画面上で確認します。期待した動きになっています。


メッセージ表示のロジックが重複しているので、共通コード部分を別メソッドとして抽出しておきます。最終的には以下のようになりました。

class StoreController < ApplicationController
  def index
    @products = Product.find_products_for_sale
  end

  def add_to_cart
    begin
      product = Product.find(params[:id])
    # 商品が見つからなかった場合の例外処理
    rescue ActiveRecord::RecordNotFound
      # エラー内容をログに残す
      logger.error('Attempt to access invalid product #{params[:id]}')
      redirect_to_index('Invalid product')
    else
      @cart = find_cart
      @cart.add_product(product)
    end
  end

  def empty_cart
    session[:cart] = nil
    redirect_to_index('Your cart is currently empty')
  end

  private
    def redirect_to_index(msg)
      flash[:notice] = msg
      redirect_to :action => :index
    end
    def find_cart
      # :cartというsessionハッシュのキーに対応する値を持たない場合、Cartオブジェクトを生成し、sessionにセットする
      session[:cart] ||= Cart.new
    end
end

最後にカート内の商品の合計金額の表示を見栄えを整えます。

まず、モデル(app/models/cart.rb)に合計金額を算出するメソッドを作成します。これは、railsで用意されているメソッドsumを使っています。

  def total_price
    @items.sum {|item| item.price}
  end

このtotal_priceメソッドを利用して、テンプレートへ合計金額を表示させます。また見栄えも整えます。

<h2>Your Pragmatic Cart</h2>
<table>
  <% for item in @cart.items %>
    <tr>
      <td><%= item.quantity %> &times;</td>
      <td><%=h item.title %></td>
      <td class="item-price"><%= number_to_currency item.price %></td>
    </tr>
  <% end %>
  <tr class="total-line">
    <td colspan="2">Total</td>
    <td class="total-cell"><%= number_to_currency @cart.total_price %></td>
</table>

<%= button_to 'Empty cart', :action => :empty_cart %>

カート内の表示が最終的には、以下のようになりました。

ある程度の作業を終わったので、サンプルアプリの作成は、ここまでで完了とします。

続きは、以下の書籍類を参考にしてください。