タスク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 %> × <%= 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 %>×</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章完了。