↓↓クリックして頂けると励みになります。
【21 | アクションケーブル(ActionCable)】 << 【ホーム】 >> 【23 | Gメール】
カートに入れた商品を購入できるようにチェックアウト機能を実装します。
ここでは、以下の機能を実装します。
- フォームからの値を取得して新しいOrderモデルオブジェクトに格納する。
- カートに入っているLineItemをその注文に追加する。
- 注文のデータが正当かどうかを検証して保存する。注文データが不正である場合はエラーメッセージを表示してユーザに修正を求める。
- 注文が正常に保存されたらカートを削除し、カタログページを再表示して注文手続きが完了したところをメッセージで通知する。
まずは「Order」モデルを作成します。
ターミナルで以下のコマンドを入力してください。
コマンド
rails generate scaffold Order name address:text email pay_type:bigint
~/Desktop/Rails7_1/SampleCart $ rails generate scaffold Order name address:text email pay_type:bigint invoke active_record create db/migrate/20240123232607_create_orders.rb create app/models/order.rb invoke test_unit create test/models/order_test.rb create test/fixtures/orders.yml invoke resource_route route resources :orders invoke scaffold_controller create app/controllers/orders_controller.rb invoke erb create app/views/orders create app/views/orders/index.html.erb create app/views/orders/edit.html.erb create app/views/orders/show.html.erb create app/views/orders/new.html.erb create app/views/orders/_form.html.erb create app/views/orders/_order.html.erb invoke resource_route invoke test_unit create test/controllers/orders_controller_test.rb create test/system/orders_test.rb invoke helper create app/helpers/orders_helper.rb invoke test_unit invoke jbuilder create app/views/orders/index.json.jbuilder create app/views/orders/show.json.jbuilder create app/views/orders/_order.json.jbuilder
次に「add_order_to_line_item.rb」というマイグレーションファイルを作成します。
ターミナルで以下のコマンドを入力してください。
rails generate migration add_order_to_line_item order:references
作成されたマイグレーションファイル「SampleCart/db/migrate/20240123232928_add_order_to_line_item.rb」を以下のように編集します。
3行目のfalseをtrueに変更しています。
4行目に記述を追加しています。
記述編集 【SampleCart/db/migrate/20240123232928_add_order_to_line_item.rb】4行目
class AddOrderToLineItem < ActiveRecord::Migration[7.1] def change add_reference :line_items, :order, null: true, foreign_key: true change_column :line_items, :cart_id, :bigint, null: true end end
マイグレーションを適用します。
マイグレーションを適用する前に、一度カートを空にするか、Posticoでline_itemsテーブルのフィールドを削除してください。
Windowsの場合はHeidiSQLでline_itemsテーブルのフィールドを削除してください。
そうしないと、「order_id」に「null」値が入っていますよというエラーが出ます。
ターミナルで以下のコマンドを入力してください。
コマンド
rails db:migrate
~/Desktop/Rails7_1/SampleCart $ rails db:migrate == 20240123232928 AddOrderToLineItem: migrating =============================== -- add_reference(:line_items, :order, {:null=>false, :foreign_key=>true}) -> 0.0106s -- change_column(:line_items, :cart_id, :bigint, {:null=>true}) -> 0.0044s == 20240123232928 AddOrderToLineItem: migrated (0.0152s) ======================
モデル
作成したOrderモデルに「pay_type(支払い方法)」の記述をしておきます。
また、LineItemへのリレーションシップの記述とバリデーションの記述も追加します。
注文を削除するときにはその注文に関連付けられたLineItemもすべて削除するように指定します。
13行目には、この後コントローラーで利用する「add_line_items_from_cart」メソッドを追加しています。
「SampleCart/app/models/order.rb」ファイルを以下のように編集します。
記述追加 【SampleCart/app/models/order.rb】
class Order < ApplicationRecord enum pay_type: { "現金" => 0, "クレジットカード" => 1, "着払い" => 2 } has_many :line_items, dependent: :destroy validates :name, :address, :email, presence: true validates :pay_type, inclusion: pay_types.keys def add_line_items_from_cart(cart) cart.line_items.each do |item| item.cart_id = nil line_items << item end end end
「SampleCart/config/locales/ja.yml」ファイルにorderテーブルのフィールドを追加して日本語化します。
記述追加 【SampleCart/config/locales/ja.yml】10行目
ja: activerecord: attributes: good: title: '商品名' description: '詳細' image_url: '画像URL' price: '価格' order: name: '氏名' address: '住所' email: 'Eメール' pay_type: '支払い方法'
LineItemからOrderへのリレーションシップを記述します。
「SampleCart/app/models/line_item.rb」ファイルの2,3行目の記述を変更しています。
「optional: true」を設定しておくと、外部キーがnilであってもデータベースに保存できるようになります。
記述変更 【SampleCart/app/models/line_item.rb】2,3行目
class LineItem < ApplicationRecord belongs_to :good belongs_to :cart, optional: true belongs_to :order, optional: true def total_price good.price * quantity end end
コントローラー
次にordersコントローラに記述を編集します。
注文をした時にカートに商品が入っていることを確認し、カートに何もない場合はユーザをトップページにリダイレクトして何が起きたかを通知します。
これによりユーザがチェックアウトページのURLを直接指定して空の注文が作成されるという事態を防止できます。
「SampleCart/app/controllers/orders_controller.rb」ファイルを以下のように編集します。
3~6行目、27行目のcreate()メソッド、78行目のensure_cart_isnt_emptyメソッドの記述を追加、変更しています。
記述編集 【SampleCart/app/controllers/orders_controller.rb】
class OrdersController < ApplicationController include CurrentCart before_action :set_cart, only: [:new, :create] before_action :ensure_cart_isnt_empty, only: :new before_action :set_order, only: [:show, :edit, :update, :destroy] # GET /orders or /orders.json def index @orders = Order.all end # GET /orders/1 or /orders/1.json def show end # GET /orders/new def new @order = Order.new end # GET /orders/1/edit def edit end # POST /orders or /orders.json def create @order = Order.new(order_params) @order.add_line_items_from_cart(@cart) respond_to do |format| if @order.save Cart.destroy(session[:cart_id]) session[:cart_id] = nil format.html { redirect_to markets_index_url, notice: '商品が注文されました。' } format.json { render :show, status: :created, location: @order } else format.html { render :new } format.json { render json: @order.errors, status: :unprocessable_entity } end end end # PATCH/PUT /orders/1 or /orders/1.json def update respond_to do |format| if @order.update(order_params) format.html { redirect_to order_url(@order), notice: "注文が変更されました。" } format.json { render :show, status: :ok, location: @order } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @order.errors, status: :unprocessable_entity } end end end # DELETE /orders/1 or /orders/1.json def destroy @order.destroy! respond_to do |format| format.html { redirect_to orders_url, notice: "注文を削除しました。" } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_order @order = Order.find(params[:id]) end # Only allow a list of trusted parameters through. def order_params params.require(:order).permit(:name, :address, :email, :pay_type) end def ensure_cart_isnt_empty if @cart.line_items.empty? redirect_to markets_index_url, notice: '商品がカートに入っていません。' end end end
ビュー
注文フォームの作成を行います。
レンダーファイル「SampleCart/app/views/orders/_form.html.erb」ファイルを以下のように編集します。
記述編集 【SampleCart/app/views/orders/_form.html.erb】
<%= form_with(model: order) do |form| %> <% if order.errors.any? %> <div style="color: red"> <h2><%= pluralize(order.errors.count, "つのエラー") %> 注文情報に不備があります。</h2> <ul> <% order.errors.each do |error| %> <li><%= error.full_message %></li> <% end %> </ul> </div> <% end %> <div class="input-group mb-3 w-75"> <span class="input-group-text">氏名</span> <%= form.text_field :name, class: "form-control" %> </div> <div class="input-group mb-3 w-75"> <span class="input-group-text">住所</span> <%= form.text_field :address, class: "form-control" %> </div> <div class="input-group mb-3 w-75"> <span for="exampleFormControlInput1" class="input-group-text">Eメール</span> <%= form.email_field :email, id: "exampleFormControlInput1", placeholder: "name@example.com", class: "form-control" %> </div> <div class="input-group mb-3 w-75"> <span class="input-group-text">お支払い方法</span> <%= form.select :pay_type, Order.pay_types.keys, prompt: '支払い方法を選択してください。', class: "form-control" %> </div> <br/> <div class="mb-3"> <%= form.submit "注文を確定する", class: "btn btn-danger w-100" %> </div> <% end %>
「SampleCart/app/views/orders/new.html.erb」ファイルを以下のように編集します。
記述編集 【SampleCart/app/views/orders/new.html.erb】
<div class="container mt-4"> <div class="row"> <div class="col-md-4"> <div id="cart" class="carts"> <%= render_if @cart && @cart.line_items.any?, @cart %> </div> </div> <div class="col-md-8"> <div class="h3 mt-4 mb-4">お客様情報を入力してください。</div> <%= render 'form', order: @order %> </div> </div> </div>
カートに「注文する」ボタンを追加します。
「SampleCart/app/views/carts/_cart.html.erb」ファイルを以下のように編集します。
25行目に<%= button_to '注文する', new_order_path, method: :get, class: "btn btn-danger w-100" %>
の記述を追加しました。
記述編集 【SampleCart/app/views/carts/_cart.html.erb】
<div class="container"> <div class="card"> <div class="card-body"> <div class="card-title h5 mb-4"><strong>現在のカート</strong></div> <% @cart.line_items.each do |item| %> <div class="card mb-2"> <div class="card-body"> <div><strong><%= item.good.title %></strong></div> <span class="badge bg-warning"><%= number_to_currency(item.good.price) %></span> × <span class="badge bg-primary"><%= item.quantity %>個</span> <div class="badge bg-danger w-100 mt-3">小計:<%= number_to_currency(item.total_price) %></div> </div> </div> <% end %> <div class="container mt-4"> <span class="badge bg-secondary">合計金額</span> <span class="badge bg-success fs-5"><%= number_to_currency(@cart.total_price) %></span> </div> <div class="mt-4"> <%= button_to 'カートを空にする', @cart, method: :delete, class: 'btn btn-outline-secondary w-100', data: { turbo: false }, form: { onSubmit: "return check()" } %> </div> <div class="mt-4"> <%= button_to '注文する', new_order_path, method: :get, class: "btn btn-danger w-100" %> </div> </div> </div> </div> <script> function check(){ if(window.confirm('カートを空にしますか?')){ return true; } else{ window.alert('キャンセルされました'); return false; } } </script>
ブラウザを確認します。
http://localhost:3000/
カートに商品を入れると「注文する」ボタンが表示されています。
入力に不備があるとバリデーションが表示されます。
注文されると、トップページにリダイレクトされ、カートが空になります。
カートが空の状態でhttp://localhost:3000/orders/new?にアクセスしても、「商品がカートに入っていません。」とメッセージが表示され、トップページにリダイレクトされます。
PosticoでOrderテーブルを確認します。
Windowsの場合はHeidiSQLで確認してください。
Orderテーブルに注文が追加されているのが確認できるはずです。
【21 | アクションケーブル(ActionCable)】 << 【ホーム】 >> 【23 | Gメール】
↓↓クリックして頂けると励みになります。