RailsでECで用いられるカート機能を解説 / 実装してみた

## 参考にした記事

[Rails5でカート機能を作るためのロジックを作ってみた](https://qiita.com/Coolucky/items/89ce3a0f25c9dfdb38c1)

おそらくこの記事を読みに来てくださる読者の方は上記の記事も読んでる可能性が高いのでモデルの設計は似せようと思います。


## STEP.1 モデル設計

```shell

rails g model product name price:integer
rails g model cart
rails g model cart_item quantity:integer product:references cart:references
```
ここらへんは記事と全く一緒です。
因みに補足としてマイグレーション部分のコードをあげとくと以下のようになると思います。
CartItemのデフォルト値のところは後から追記してます。

### マイグレーションの内容
```ruby
class CreateProducts < ActiveRecord::Migration[6.0]
  def change
    create_table :products do |t|
      t.string :name
      t.integer :price

      t.timestamps
    end
  end
end


class CreateCarts < ActiveRecord::Migration[6.0]
  def change
    create_table :carts do |t|

      t.timestamps
    end
  end
end


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
```

上記の内容でマイグレートを通した後であればコンソールで軽く遊んでみたりしてデフォルト値が設定されているかとか確認しておくと良いかもですね。

<img width="1272" alt="スクリーンショット 2019-11-12 9.58.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195292/5bea535a-bf8f-2cc6-2999-c4716f7bb56c.png">

(因みに補足ですがCartItemの外部キーは存在しないものを指定するとrollbackするので先にCartとProductを作らないとハマります) 


### アソシエーションの内容

```ruby

class Product < ApplicationRecord
end

class Cart < ApplicationRecord
  has_many :cart_items
end

class CartItem < ApplicationRecord
  belongs_to :product
  belongs_to :cart
end
```

※参考記事と一緒

因みに、もしここのアソシエーションを指定している意味が曖昧な人のために↓
このアソシエーションを追加することで以下のように親のモデルから関連する子のモデルが抽出出来たりするってことですね。

`Cart.fitst.cart_items` な感じで

<img width="1269" alt="スクリーンショット 2019-11-12 10.08.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195292/36ec322b-8aae-991e-3283-a50f2ca7ed6e.png">

詳しくは本家の [Active Record の関連付け](https://railsguides.jp/association_basics.html) を


## STEP.2 コントローラー設計

次にコントローラーですが無駄なコードは極力無くしてます。
例えばこの記事では一旦、View側まで書かないので `helper_method` などは記述してません。
必要に応じて設定して頂ければと思います。

```ruby

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  def current_cart
    Cart.find(session[:cart_id])
  rescue ActiveRecord::RecordNotFound
    cart = Cart.create
    session[:cart_id] = cart.id
    cart
  end
end
```

個人的に `find` を使う場合はできるだけ例外処理で丁寧に扱うことを意識しています。

```ruby

class CartsController < ApplicationController
  before_action :setup_cart_item!, only: [:add_item, :update_item, :delete_item]

  def show
    @cart_items = current_cart.cart_items
  end

  def add_item
    if @cart_item.blank?
      @cart_item = current_cart.cart_items.build(product_id: params[:product_id])
    end

    @cart_item.quantity += params[:quantity].to_i
    @cart_item.save
    redirect_to ''
  end

  def update_item
    @cart_item.update(quantity: params[:quantity].to_i)
    redirect_to ''
  end

  def delete_item
    @cart_item.destroy
    redirect_to ''
  end

  private

  def setup_cart_item!
    @cart_item = current_cart.cart_items.find_by(product_id: params[:product_id])
  end
end

```

`setup_cart_item!` 部分でインスタンス変数を使ってますがViewで引っ張ってくる予定がないのであれば普通の変数にリファクタリングして良いと思います。

またリダイレクト部分はよしなに変更していけば良いかなと


## STEP.3 ルーティング設定

参考記事通り以下の部分を追加すればカート周りは大丈夫そうですね。

```ruby
resource :carts, only: [:show]
post '/add_item' => 'carts#add_item'
post '/update_item' => 'carts#update_item'
delete '/delete_item' => 'carts#delete_item'
```