131214_01:アジャイル開発本
第9章
タスクD:カートの作成
この章で学ぶこと
- セッションおよびセッション管理
- モデル間のリレーションショップの追加
- カートに商品を入れるボタンの追加
9.1 イテレーションD1 : カートの取得
depot> rails generate scaffold cart
depot> rake db:migrate
カートをシンボル : cart_idというキーでセッションに格納する。
9.2 イテレーションD2:商品とカートの関連付け
depot> rails generate scaffold line_item product_id:integer cart_id:integer
depot> rake db:migrate
これでデータベースに、品目、カート、商品の間のリレーションシップを格納する場所ができた。
次に、これらの相互関係を指定する宣言をモデルファイルに追加。
cart.rubyに
has_many :line_items, dependent: :destroy
を追加
これで、1つのカートに多数の品目が関連付けられる。
dependent: :destroyという部分は、品目が存在するかどうかは、カートが存在するかどうかに依存するという意味。
次に、品目からCartsおよびproductsテーブルへの逆方向のリンクを指定。
line_item.rbファイル内でbelong_to( )宣言を2回使う
belongs_to :product
belongs_to :cart
belongs_toはRailsに、line_itemsテーブルの行はcartsテーブルとproductsテーブル内にある行の子であることを通知。
いずれカートが増えたとき、各商品が、その商品を参照する多数の品目を持つ可能性があるため、Productモデルにhas_manyディレクティブを追加。
models/product.rb
# encoding: utf-8
class Product < ActiveRecord::Base
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item
validates :title, :description, :image_url, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)$}i,
message: 'はGIF、JPG、PNG画像のURLでなければなりません'
}
private
# この商品を参照している品目が無いことを確認する
def ensure_not_referenced_by_any_line_item
if line_items.empty?
return true
else
errors.add(:base, '品目が存在します')
return false
end
end
end
ここでは、ensure_not_referenced_by_any_line_itemというフックメソッドを定義している。
フックメソッドとは、オブジェクトのライフサイクルの特定の時点でRailsから自動的に呼び出されるメソッド。
9.3 イテレーションD3 : ボタンの追加
次は、各商品に「カートに入れるボタンを追加」
index.html.erbに次の1行を追加
<% button_to 'カートに入れる', line_items_path(product_id: product) %>
ボタンは価格の横に置きたいのでこれらを一列に並べるちょっとしたCSSのマジックを追加
app/assets/stylesheets/store.css.scssのentry用のルールの中に
p, div.price_line {
margin-left: 100px;
margin-top: 0.5em;
margin-bottom: 0.8em;
form, div {
display: inline;
}
}
を追加
app/controllers/line_items_controller.rbのcreate( )メソッドのコードに何行か手を加える。
更に、コントローラの機能を変更したので、機能テストの該当箇所も更新する必要がある。createの呼び出しで商品idを私、リダイレクトの対象として期待されるものを変更する必要がある。
test/functional/line_items_controller_test.rbを修正
# encoding: utf-8
#---
# Excerpted from "Agile Web Development with Rails, 4th Ed.",
# published by The Pragmatic Bookshelf.
# Copyrights apply to this code. It may not be used to create training material,
# courses, books, articles, and the like. Contact us if you are in doubt.
# We make no guarantees that this code is fit for any purpose.
# Visit http://www.pragmaticprogrammer.com/titles/rails4 for more book information.
#
# 日本語版については http://ssl.ohmsha.co.jp/cgi-bin/menu.cgi?ISBN=978-4-274-06866-9
#---
class LineItemsController < ApplicationController
# GET /line_items
# GET /line_items.json
def index
@line_items = LineItem.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @line_items }
end
end
# GET /line_items/1
# GET /line_items/1.json
def show
@line_item = LineItem.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @line_item }
end
end
# GET /line_items/new
# GET /line_items/new.json
def new
@line_item = LineItem.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: @line_item }
end
end
# GET /line_items/1/edit
def edit
@line_item = LineItem.find(params[:id])
end
# POST /line_items
# POST /line_items.json
def create
@cart = current_cart
product = Product.find(params[:product_id])
@line_item = @cart.add_product(product.id)
respond_to do |format|
if @line_item.save
format.html { redirect_to @line_item.cart,
notice: 'Line item was successfully created.' }
format.json { render json: @line_item,
status: :created, location: @line_item }
else
format.html { render action: "new" }
format.json { render json: @line_item.errors,
status: :unprocessable_entity }
end
end
end
# PUT /line_items/1
# PUT /line_items/1.json
def update
@line_item = LineItem.find(params[:id])
respond_to do |format|
if @line_item.update_attributes(params[:line_item])
format.html { redirect_to @line_item, notice: 'Line item was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: @line_item.errors, status: :unprocessable_entity }
end
end
end
# DELETE /line_items/1
# DELETE /line_items/1.json
def destroy
@line_item = LineItem.find(params[:id])
@line_item.destroy
respond_to do |format|
format.html { redirect_to line_items_url }
format.json { head :ok }
end
end
end
カートを生成したときに表示
この章のまとめ
- 1回のリクエストでCartオブジェクトを作成し、セッションオブジェクトを使うことで、2回目以降のリクエストでも同じカートを取得することができた。
- すべてのコントローラのベースクラスにprivateメソッドを追加し、どのコントローラからも利用できるようにした。
- カートと品目の間のリレーションシップ、および品目と商品の間のリレーションシップを構築し、これらのリレーションシップを使った移動ができるようになった。
- 商品をカートに入れるボタンを追加し、クリック時に新しい品目が作成されるようにした。