環境
Ruby 2.6.6
Rails 6.0.3
実装機能
簡単なECサイトのカート機能を作成します。
ログインユーザーにはユーザーに紐づいたカート情報を呼び出し、未ログインユーザーにはsessionを利用します。
前提
ユーザーのログインのためにdeviseを導入しています。 (モデル名はUser)
モデル
※Userモデルは省いています
カートモデルを作成することでカート内アイテムを管理しています。
class CreateCartItems < ActiveRecord::Migration[6.0]
def change
create_table :cart_items do |t|
t.integer :quantity, default: 0
t.references :product, null: false, foreign_key: true
t.references :cart, null: false, foreign_key: true
t.timestamps
end
end
end
class CreateCarts < ActiveRecord::Migration[6.0]
def change
create_table :carts do |t|
# sessionで管理する場合user_idはnullになるのでnull: falseは不要
t.references :carts, :user, foreign_key: true
t.timestamps
end
end
end
class Cart < ApplicationRecord
has_many :cart_items, dependent: :destroy
end
class CartItem < ApplicationRecord
belongs_to :product
belongs_to :cart
# カート内の商品合計に利用
def sum_of_price
product.price * quantity
end
end
class User < ApplicationRecord
has_one :cart, dependent: :destroy
end
現在のカートを参照するメソッドの定義
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_cart
def current_cart
if current_user
# ユーザーとカートの紐付け
current_cart = current_user.cart || current_user.create_cart!
else
# セッションとカートの紐付け
current_cart = Cart.find_by(id: session[:cart_id]) || Cart.create
session[:cart_id] ||= current_cart.id
end
current_cart
end
end
ユーザーがログインしている場合、紐づいたカートを参照し、なければ作成
ユーザーがいない場合、sessionからカートを参照しなければ作成しています。
ルーティング設定
Rails.application.routes.draw do
.
.
.
get '/my_cart' => 'carts#my_cart'
post '/add_item' => 'carts#add_item'
post '/update_item' => 'carts#update_item'
delete '/delete_item' => 'carts#delete_item'
end
他のユーザーのカートを見る必要がないため、showアクションを使わずにカートの中身を見るページを作成しています。
コントローラーの作成
class CartsController < ApplicationController
before_action :setup_cart_item!, only: %i[add_item update_item delete_item]
# カート内アイテムの表示
def my_cart
@cart_items = current_cart.cart_items.includes([:product])
@total = @cart_items.inject(0) { |sum, item| sum + item.sum_of_price }
end
# アイテムの追加
def add_item
@cart_item ||= current_cart.cart_items.build(product_id: params[:product_id])
@cart_item.quantity += params[:quantity].to_i
if @cart_item.save
flash[:notice] = '商品が追加されました。'
redirect_to my_cart_path
else
flash[:alert] = '商品の追加に失敗しました。'
redirect_to product_url(params[:product_id])
end
end
# アイテムの更新
def update_item
if @cart_item.update(quantity: params[:quantity].to_i)
flash[:notice] = 'カート内のギフトが更新されました'
else
flash[:alert] = 'カート内のギフトの更新に失敗しました'
end
redirect_to my_cart_path
end
# アイテムの削除
def delete_item
if @cart_item.destroy
flash[:notice] = 'カート内のギフトが削除されました'
else
flash[:alert] = '削除に失敗しました'
end
redirect_to my_cart_path
end
private
def setup_cart_item!
@cart_item = current_cart.cart_items.find_by(product_id: params[:product_id])
end
end
補足としましては、my_cartアクションでは先にカート内商品の合計金額を計算しております。
(他にきれいな方法があるかもしれません…誰かわかる方教えてください!)
他のアクションでは基本的にparamsに渡したproduct_idを元に、カート内の商品に変更を加えていくといった感じです。
ビューの作成(カートのみ)
<h2>カート内アイテム</h2>
<%= render partial: "carts/cart_item", collection: @cart_items, as: "cart_item"%>
<h2><%= "合計:#{@total}円" %></h2>
<li class="list-none">
<%= cart_item.product.name %>
<%= cart_item.product.price %>
<%= form_with(url: update_item_url, method: :post,local: true) do |f| %>
<%= f.hidden_field :product_id, value: cart_item.product.id %>
<%= f.number_field :quantity ,value: cart_item.quantity%>
<%= f.submit "更新", class: "" %>
<% end %>
<%= button_to "削除", delete_item_path(product_id: cart_item.product.id), method: :delete,
data: { confirm: "商品を削除しますか?" } %>
</li>
最低限のビューですが、カート内の商品を編集できるようになりました。
課題
ユーザーと紐づいたカートモデルは決済やログアウトのタイミングで削除すれば良いですが、sessionで管理しているカートモデルのデータを削除するタイミングが決済以外ないので、rake taskで定期的に削除した方がいいかもしれません…!
参考