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メソッドを追加し、どのコントローラからも利用できるようにした。
  • カートと品目の間のリレーションシップ、および品目と商品の間のリレーションシップを構築し、これらのリレーションシップを使った移動ができるようになった。
  • 商品をカートに入れるボタンを追加し、クリック時に新しい品目が作成されるようにした。