Rails 初学者がECサイトの注文機能について復習

## この記事を書いた目的
- Railsを学びはじめて2ヶ月目。プログラミング学習も2ヶ月目の初学者が復習を兼ねて躓いた部分を投稿します。
- 間違っている部分がありましたらご指摘をお願いいたします。

- 追記
今見返すと色々無駄が多いです。
必要ない記述とかあってお恥ずかしい限りですが、自身がこれだけわかっていなかったんだということでこのままにしておきます。

## 行いたいこと
- 顧客は購入するときに配送先を選ぶことができ、新規登録も出来る
    - このときにどうやって値を保持すれば良いのか分からなかったのでそれを復習します
    - session は理解不足なので使いません

## 用意するもの・使用したバージョン
|使用するもの|バージョン|
|:--|:--|
|Ruby|2.6.3|
|Rails|5.2.5|

|作成するテーブル|内容|
|:--|:--|
|Customer|顧客データ|
|Address|顧客の配送先データ|
|Item|商品データ|
|CartItem|商品をカートに入れる|
|Order|注文情報の記録|
|OrderItem|商品ごとの個別の注文情報|

|Customer|データ型|配送先住所のテーブル|
|:--|:--|:--|
|name|string|ユーザーの名前|
|customer_address|string|初回登録時の配送先住所|

|Address||データ型|追加の配送先住所のテーブル|
|:--|:--|:--|:--|
|customer_id|FK|integer|Customerとの関連付け|
|address_name||string|追加の配送先の宛先|
|address||string|追加の配送先住所|

|Item|データ型|商品に関するテーブル|
|:--|:--|:--|
|item_name|string|商品名|
|price|integer|金額|

|CartItem||データ型|カートに追加した商品のテーブル|
|:--|:--|:--|:--|
|customer_id|FK|integer|Customerとの関連付け|
|item_id|FK|integer|Itemとの関連付け|
|quantity||integer|購入個数|

|Order||データ型|注文情報を保存するテーブル|
|:--|:--|:--|:--|
|customer_id|FK|integer|Customerとの関連付け|
|order_name||string|注文時の宛先|
|order_address||string|配送先住所|
|total_price||integer|合計金額|

|OrderItem||データ型|商品ごとの注文情報を保存するテーブル|
|:--|:--|:--|:--|
|item_id|FK|integer|Itemとの関連付け|
|order_id|FK|integer|Orderとの関連付け|
|order_quantity||integer|購入個数|
|order_price||integer|商品ごとの小計|

今回はわかりやすさ重視ですべてのカラム名を分けています。
Customer は device を使用しましたが device については割愛します
カラムは動作を確認するための最低限のものです

## 実際の記述
- 前提事項
    - カートにはすでに関連付けられた商品が入っているものとします
    - 簡素化するために今回は税込みなどは考えません
    - それぞれのカラムは Not Null 制約を行っているものとします

### model

```ruby:customer.rb
has_many :addresses
has_many :cart_items
has_many :orders
```

```ruby:address.rb
belongs_to :customer
```

```ruby:item.rb
has_many :cart_items
has_many :order_items
```

```ruby:cart_item.rb
belongs_to :order
belongs_to :item

def sum_price # 実際に作成したサイトは税金も算出していたのでメソッドを記載していました
  item.taxin_price*quantity
end
```

合計金額を算出するメソッドについてはこちらを参考にしました。
[カート機能①カート内商品の合計金額を出す inject 税込価格の表示 / ECサイト](https://qiita.com/ki_87/items/f1771b2bda278828a54a)

```ruby:order.rb
belongs_to :customer
has_many :order_items
```

```ruby:order_item.rb
belongs_to :order
belongs_to :item
```

### controller
```ruby:orders_controller.rb
# 購入情報の入力画面で、宛先や住所などを入力するところです
def new
  @order = Order.new
end

# 購入を確定します
def create # Order に情報を保存します
  cart_items = current_customer.cart_items.all
# ログインユーザーのカートアイテムをすべて取り出して cart_items に入れます
  @order = current_customer.orders.new(order_params)
# 渡ってきた値を @order に入れます
  if @order.save
# ここに至るまでの間にチェックは済ませていますが、念の為IF文で分岐させています
    cart_items.each do |cart|
# 取り出したカートアイテムの数繰り返します
# order_item にも一緒にデータを保存する必要があるのでここで保存します
      order_item = OrderItem.new
      order_item.item_id = cart.item_id
      order_item.order_id = @order.id
      order_item.order_quantity = cart.quantity
# 購入が完了したらカート情報は削除するのでこちらに保存します
      order_item.order_price = cart.item.price
# カート情報を削除するので item との紐付けが切れる前に保存します
      order_item.save
    end
    redirect_to 遷移したいページのパス
    cart_items.destroy_all
# ユーザーに関連するカートのデータ(購入したデータ)をすべて削除します(カートを空にする)
  else
    @order = Order.new(order_params)
    render :new
  end
end

# new 画面から渡ってきたデータをユーザーに確認してもらいます
def check
  @order = Order.new(order_params)
# new 画面から渡ってきたデータを @order に入れます
  if params[:order][:address_number] == "1"
# view で定義している address_number が"1"だったときにこの処理を実行します
# form_with で @order で送っているので、order に紐付いた address_number となります。以下同様です
# この辺の紐付けは勉強不足なので gem の pry-byebug を使って確認しながら行いました
    @order.name = current_customer.name # @order の各カラムに必要なものを入れます
    @order.address = current_customer.customer_address
  elsif params[:order][:address_number] == "2"
# view で定義している address_number が"2"だったときにこの処理を実行します
    if Address.exists?(name: params[:order][:registered])
# registered は viwe で定義しています
      @order.name = Address.find(params[:order][:registered]).name
      @order.address = Address.find(params[:order][:registered]).address
    else
      render :new
# 既存のデータを使っていますのでありえないですが、万が一データが足りない場合は new を render します
    end
  elsif params[:order][:address_number] == "3"
# view で定義している address_number が"3"だったときにこの処理を実行します
    address_new = current_customer.addresses.new(address_params)
    if address_new.save # 確定前(確認画面)で save してしまうことになりますが、私の知識の限界でした
    else
      render :new
# ここに渡ってくるデータはユーザーで新規追加してもらうので、入力不足の場合は new に戻します
    end
  else
    redirect_to 遷移したいページ # ありえないですが、万が一当てはまらないデータが渡ってきた場合の処理です
  end
  @cart_items = current_customer.cart_items.all # カートアイテムの情報をユーザーに確認してもらうために使用します
  @total = @cart_items.inject(0) { |sum, item| sum + item.sum_price }
# 合計金額を出す処理です sum_price はモデルで定義したメソッドです
end

private

def order_params
  params.require(:order).permit(:name, :address, :total_price)
end

def address_params
  params.require(:order).permit(:name, :address)
end
```
### view
レイアウトは調整していません

```erb:orders/new.html.erb
<!-- @order でラジオボタンを使って情報を送ります -->
<%= form_with model: @order, url: check アクションにつながるパス, local: true do |f| %>
<!-- customer テーブルに登録されている住所の場合です -->
  <%= f.radio_button :address_number, 1 %> <!-- address_number と定義して1～3の番号を check に送ります -->
  <%= f.label :address_number_1, "ご自身の住所" %>
  <%= current_customer.customer_address %>

<!-- 登録されたaddressから選んでもらう場合です -->
  <%= f.radio_button :address_number, 2 %>
  <%= f.label :address_number_2, "登録先の住所" %>
  <%= f.collection_select(:registered, current_customer.addresses.all, :id, :address %>
<!-- registered として current_customer.address.all で取得した id を送ります。
     ユーザーに表示して選択してもらうのは address です -->
<!-- ドロップダウンを選択したら自動でラベルも選択されるようにしたかったのですが、現状ではわかりませんでした -->

<!-- 新しい住所を追加する場合です -->
  <%= f.radio_button :address_number, 3 %>
  <%= f.label :address_number_3, "新しいお届け先" %>
  <%= f.label :address, "住所" %>
  <%= f.text_field :address %>
  <%= f.label :address_name, "宛名" %>
  <%= f.text_field :address_name %>
<!-- 選んでもらったデータを送ります -->
  <%= f.submit "確認画面へ進む" %>
<% end %>
```
```erb:orders/check.html.erb
<!-- ユーザーに確認してもらうための表示です -->
<% @cart_items.each do |cart| %>
  <%= cart.item.name %> <!-- 商品名 -->
  <%= (cart.item.price).to_s(:delimited) %>　<!-- 金額 -->
  <%= cart.quantity %> <!-- 個数 -->
  <%= (cart.item.price*cart.quantity).to_s(:delimited) %> <!-- 小計 -->
<% end %>
<p>ご請求金額</p>
<%= @total.to_s(:delimited) %> <!-- お買い物の合計金額 -->
<p>お届け先</p>
<%= @order.address %>

<!-- ユーザーには注文確定ボタンだけ表示します -->
<%= form_with model: @order, url: create へのパス, local: true do |f| %>
  <%= f.hidden_field :name %>
  <%= f.hidden_field :address %>
  <%= f.hidden_field :total_price, value: @total %>
  <%= f.submit "注文を確定する" %>
<% end %>
```

後は注文履歴を表示する画面などで実際に表示できていれば成功です

## おわりに
学習時に手伝っていただいたメンターの方、ありがとうございました。

実際に誰かに伝えるようにすることで、自分の書いたコードの無駄を発見できました。
今の私にはわかりませんが、ここに書いてあるコードにも実際には無駄が多くあるかも知れません。
もっともっと学習が必要だと思うきっかけになりました。

ここまでご覧下さりありがとうございました。