気軽に楽しくプログラムと遊ぶ

自分が興味があってためになるかもって思う情報を提供しています。

書籍「Railsによるアジャイル Webアプリケーション開発」10章

タスクE:もっとスマートなカート

10.1 イテレーションE1:もっとスマートなカートの作成

カートに同じ商品を追加した場合に、2件と表示できるようにテーブル定義を更新する。

depot> rails generate migration add_quantity_to_line_items quantity:integer
depot> rake db:migrate

カートへ商品を追加した際にquantity(数量)が1となるようにデフォルト値を設定する

class AddQuantityToLineItems < ActiveRecord::Migration
  def change
    add_column :line_items, :quantity, :integer, default:1
  end
end


商品追加メソッドを追加する

depot/app/models/cart.rb

  def add_product (product_id)
    #追加商品IDの存在確認を行う
    current_item = line_items.find_by_product_id(product_id)
    if current_item
      #すでに存在する場合、現在の商品の数量を1増加させる
      current_item.quantity += 1
    else
      #存在しない場合、指定商品IDで商品を追加する
      current_item = line_items.build(product_id: product_id)
    end
    # javaにおける return current_itemと同義
    current_item


品目コントローラーで追加メソッドを使用する

/depot/app/controllers/line_items_controller.rb

  def create
    ・・・
    #取得したProductをcartのline_itemsへ紐づける。Productをインスタンス変数へ格納
    @line_item = @cart.line_items.add_product(product.id)


カート内商品の数量を変更する

/depot/app/controllers/line_items_controller.rb

<h2>Pragmaticカート</h2>
<ul>
  <% @cart.line_items.each do |item| %>
  <li><%= item.quantity %> &times; <%= item.product.title %></li>
  <% end %>
</ul>


表示してみると数量が2の商品と元々追加していた同商品が混在して表示される。
最初に追加した商品をまとめるという処理が必要。
データのマイグレーションを行うための処理を実装する。

depot> rails generate migration combine_items_in_cart

/depot/db/migrate/20150405014225_combine_items_in_cart.rb

class CombineItemsInCart < ActiveRecord::Migration
  #DBデータ変更用メソッド
  def up
    Cart.all.each do |cart|
      #カート内商品をproduct_idでまとめ、数量を合計。
      sums = cart.line_items.group(:product_id).sum(:quantity)

      #product_id,quantityの配列リストを分解
      sums.each do |product_id, quantity|
        if quantity > 1
          #該当product_idの商品を全削除
          cart.line_items.where(product_id: product_id).delete_all
          #新たに対象product_idの商品をquantityに合計数量を設定した状態で作成
          cart.line_items.create(product_id: product_id, quantity: quantity)
        end
      end
    end
  end
end

マイグレーションの原則の一つに各ステップは
戻せなくてはならないという原則があるそう。よってupの反対のdownも作成する

/depot/db/migrate/20150405014225_combine_items_in_cart.rb

  #DBデータ変更用メソッドの変更内容を元に戻すメソッド
  def down
    #数量が1より大きいのカート内商品を数量1の商品に分割する
    LineItem.where("quantity>1").each do |line_item|
      line_item.quantity.times do
        LineItem.create(cart_id: line_item.cart_id, product_id: line_item.product_id, quantity: 1)
      end
      #元の商品は削除する
      line_item.destroy
    end
  end


マイグレーションの実行方法は

depot> rake db:rollback

上記により、downメソッドが起動し、upメソッドで変更した内容を
元に戻す事が可能となる。


次に、セキュリティー対策の実装を行う。
現状、下記URLを入力するとRecordNotFound例外発生の画面が表示されてしまう。

http://localhost:3000/carts/wibble


これに対処するために、下記を実施する。

・ログへのエラー情報の書き出し
・画面へ「無効なカートです」のエラーメッセージの表示

/depot/app/controllers/carts_controller.rb

      begin
        @cart = Cart.find(params[:id])
      rescue ActiveRecord::RecordNotFound
        # 全コントローラーが保持するlogger属性を用いてログを記録
        logger.error "無効なカート#{params[:id]}にアクセスしようとしました"
        # noticeへ設定する事でフラッシュへメッセージを設定
        redirect_to store_url, notice: '無効なカートです'
      else
        respond_to do |format|
          format.html #show.html.erb
          format.json {render json: @cart}
        end
      end

エラー時にリダイレクトで商品一覧を表示する理由は、URLをリダイレクト先のURLするため。
エラーとなるcart_idが再表示されることを防ぐ。

フラッシュとは2リクエスト内で情報保持できる保存領域。


10.3 イテレーションE3: カートの仕上げ

カートを空にする機能の実装を行う

depot/app/views/carts/show.html.erb

<%= button_to 'カートを空にする', @cart, method: :delete, confirm: '本当によろしいですか?'%>

表示カートのインスタンスを渡しつつ、HTTPのdeleteとして、リクエストを発行するボタンを追加


depot/app/controllers/carts_controller.rb

 def destroy
    #現在のカートインスタンスを削除
    @cart = current_cart
    @cart.destroy
    #セッション情報のカートidをクリア
    session[:cart_id] = nil

    respond_to do |format|
      #商品一覧画面へ遷移させ、カートが空の旨のメッセージ表示
      format.html { redirect_to store_url, notice: 'カートは現在空です' }
      format.json { head :ok }
    end
  end

ちなみにformat.html,format.json
URLに指定したファイル形式によって切り替わる表示方式を指定している。

例) indexメソッドの呼び出しの場合

localhost:8080/index → format.htmlのパターンで返却
localhost:8080/index.json → format.jsonのパターンで返却


product_url,product_pathの違い

product_url:プロトコルドメインを含むURL全体を生成(別ドメイン、リダイレクトによるアクセスの場合用いる)
product_path:コントローラー名以下を生成(上記以外の場合、用いる)


カートの表示を整えます。

depot/app/views/carts/show.html.erb

<div class="cart_title">カート</div>
<table>
  <% @cart.line_items.each do |item| %>
  <tr>
  <!-- 各商品の個数、タイトル、合計金額を表示 -->
  <% @cart.line_items.each do |item| %>
  <tr>
    <td><%= item.quantity %>&times;</td>
    <td><%= item.product.title %></td>
    <td class="item_price"><%= number_to_currency(item.total_price) %></td>
  </tr>
  <% end %>

  <!-- カート内の商品の合計金額を表示 -->
  <tr class="total_line">
    <td colspan="2">合計</td>
    <td class="total_cell"><%= number_to_currency(@cart.total_price) %></td>
  </tr>

</table>


品目の合計金額の返却メソッドを作成する

depot/app/models/line_item.rb

  def total_price
    product.price * quantity
  end


カート内の全商品の合計金額の返却メソッドを作成する

depot/app/models/cart.rb

  def total_price
    line_items.to_a.sum { |item| item.total_price }
  end

自由課題
→難しくて、時間がかかりそうだったので。一旦パス。

10章完了。