<<前 [TOP] 次>>
カートの各商品に数量を対応付けるためには「line_itemsテーブル」を修正する必要があります。
コマンドプロンプトで「bin」フォルダに移動して「rails generate migration add_quantity_to_line_items quantity:integer
」と入力します。
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 %> × <%= 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()」メソッドが実行されます。
↓↓クリックして頂けると励みになります。