学生向けプログラミング入門

学生向けにプログラミングを解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails

Ruby on Rails 6.0によるWebアプリケーション開発21 カートの改良

<<前  [TOP]  次>>


カートの各商品に数量を対応付けるためには「line_itemsテーブル」を修正する必要があります。
コマンドプロンプトで「bin」フォルダに移動して「rails generate migration add_quantity_to_line_items quantity:integer」と入力します。

「line_itemsテーブル」の修正
「line_itemsテーブル」の修正


Railsはマイグレーションの名前から「line_itemsテーブル」に列を追加しようとしているのだと認識し、各列の名前とデータ型を最後の引数から取得します。
Railsが認識するパターンは「add_XXX_to_TABLE」と「remove_XXX_from_TABLE」の2つです。
XXXの部分は無視されます。
Railsに判断できないのはこの列にはどんなデフォルト値が適切かということだけです。
多くの場合はnull値で問題ありませんが、既存のカートのデフォルト値が1となるようにマイグレーションを適用する前に次のように修正します。
「C:\Rails6\work\shop\db\migrate」フォルダに出来た「add_quantity_to_line_items.rb」ファイルを以下のように編集します。


【C:\Rails6\work\shop\db\migrate\add_quantity_to_line_items.rb】

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



修正が済んだらマイグレーションを実行します。
コマンドプロンプトで「bin」フォルダに移動して「rails db:migrate」と入力します。

マイグレーションの実行
マイグレーションの実行


次に「add_good()」メソッドをCartモデルに作成します。
このメソッドはこれから追加しようとしている商品がLineItemsに含まれているかどうか確認して、含まれていれば数量を増やして含まれていなければ新たにLineItemを作成します。
「C:\Rails6\work\shop\app\models」フォルダにある「cart.rb」ファイルに「add_goog()」メソッドの記述を追加します。


【C:\Rails6\work\shop\app\models\cart.rb】

class Cart < ApplicationRecord
	has_many :line_items, dependent: :destroy

	def add_good(good)
		current_item = line_items.find_by(good_id: good.id)
		if current_item
			current_item.quantity += 1
		else
			current_item = line_items.build(good_id: good.id)
		end
		current_item

	end
end



「line_items」コントローラにも修正を加えてこの「add_goog()」メソッドを使用するようにします。
「C:\Rails6\work\shop\app\controllers」フォルダにある「line_items_controller.rb」ファイルを以下のように編集します。
修正するのは「create()」メソッドの「@line_item = @cart.add_good(good)」の部分です。


【C:\Rails6\work\shop\app\controllers\line_items_controller.rb】

class LineItemsController < ApplicationController

	include CurrentCart
	before_action :set_cart, only: [:create]
	before_action :set_line_item, only: [:show, :edit, :update, :destroy]

  # GET /line_items
  # GET /line_items.json
  def index
    @line_items = LineItem.all
  end

  # GET /line_items/1
  # GET /line_items/1.json
  def show
  end

  # GET /line_items/new
  def new
    @line_item = LineItem.new
  end

  # GET /line_items/1/edit
  def edit
  end

  # POST /line_items
  # POST /line_items.json
  def create

	good = Good.find(params[:good_id])
	@line_item = @cart.add_good(good)

    respond_to do |format|
      if @line_item.save
        format.html { redirect_to @line_item.cart, notice: '品目が作成されました。' }
        format.json { render :show, status: :created, location: @line_item }
      else
        format.html { render :new }
        format.json { render json: @line_item.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /line_items/1
  # PATCH/PUT /line_items/1.json
  def update
    respond_to do |format|
      if @line_item.update(line_item_params)
        format.html { redirect_to @line_item, notice: '品目が更新されました。' }
        format.json { render :show, status: :ok, location: @line_item }
      else
        format.html { render :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.destroy
    respond_to do |format|
      format.html { redirect_to line_items_url, notice: '品目を破棄しました。' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_line_item
      @line_item = LineItem.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def line_item_params
      params.require(:line_item).permit(:good_id, :cart_id)
    end
end



「show」ビューにも変更を加えます。
「C:\Rails6\work\shop\app\views\carts」フォルダにある「show.html.erb」ファイルを以下のように編集します。

<% if notice %>
	<aside id="notice"><%= notice %></aside>
<% end %>

<h2>カートの内容</h2>

<ul>
	<% @cart.line_items.each do |item| %>
		<li><%= item.quantity %> &times; <%= item.good.title %></li>
	<% end %>
</ul>



必要に応じて複数の行を1つにまとめるという処理を行います。
まずはマイグレーションを作成します。
コマンドプロンプトで「bin」フォルダに移動し、「rails generate migration combine_items_in_cart」と入力します。

マイグレーションの作成
マイグレーションの作成


Railsは「combine_items_in_cart」という名前が理解できないので生成されている「change()」メソッドを「up()」「down()」という別のメソッドに書き換えなくてはなりません。
まずはupメソッドの実装です。
「C:\Rails6\work\shop\db\migrate」フォルダに作成された「combine_items_in_cart.rb」ファイルを以下のように編集します。


【C:\Rails6\work\shop\db\migrate\combine_items_in_cart.rb】

class CombineItemsInCart < ActiveRecord::Migration[6.0]
  def up
	# カート内に1つの商品に対して複数のLineItemがあった場合は1つのLineItemに置き換える
	Cart.all.each do |cart|
		# カート内の各商品の数をカウントする
		sums = cart.line_items.group(:good_id).sum(:quantity)

		sums.each do |good_id, quantity|
			if quantity > 1
				# 個別のLineItemを削除する
				cart.line_items.where(good_id: good_id).delete_all

				# 1つのLineItemに置き換える
				item = cart.line_items.build(good_id: good_id)
				item.quantity = quantity
				item.save!
			end
		end
	end
  end
end



マイグレーションを実行します。
コマンドプロンプトで「bin」フォルダに移動し、「rails db:migrate」と入力します。

マイグレーションの実行
マイグレーションの実行


カートに商品を何個か追加してブラウザ表示を確認します。
ブラウザの表示
ブラウザの表示


マイグレーションの重要な原則の一つに各ステップが元に戻せなくてはならないというものがあります。
つまり「down()」メソッドも追加しなくてはなりません。
このメソッドでは数量が2以上のLineItemを見つけ、このカートと商品にそれぞれ数量1で新しいLineItemを追加し最後に元のLineItemを削除します。
先ほどの「combine_items_in_cart.rb」ファイルに「down()」メソッドの記述を追加します。


【C:\Rails6\work\shop\db\migrate\combine_items_in_cart.rb】

class CombineItemsInCart < ActiveRecord::Migration[6.0]
  def up
	# カート内に1つの商品に対して複数のLineItemがあった場合は1つのLineItemに置き換える
	Cart.all.each do |cart|
		# カート内の各商品の数をカウントする
		sums = cart.line_items.group(:good_id).sum(:quantity)

		sums.each do |good_id, quantity|
			if quantity > 1
				# 個別のLineItemを削除する
				cart.line_items.where(good_id: good_id).delete_all

				# 1つのLineItemに置き換える
				item = cart.line_items.build(good_id: good_id)
				item.quantity = quantity
				item.save!
			end

		end
	end

  end

  def down
	# 数量>1 のLineItemを複数のLineItemに分割する
	LineItem.where("quantity >1").each do |line_item|

		# 個別のLineItemを追加する
		line_item.quantity.times do
			LineItem.create(
				cart_id: line_item.cart_id,
				good_id: line_item.good_id,
				quantity: 1
			)
		end

		# 元のLineItemを削除する
		line_item.destroy
	end
  end
end



今回は行いませんが、「rails db:rollback」とすれば「down()」メソッドが実行されます。


<<前  [TOP]  次>>