学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Rails導入編 | カート機能の実装 | 22 | チェックアウト

↓↓クリックして頂けると励みになります。



21 | アクションケーブル(ActionCable)】 << 【ホーム】 >> 【23 | Gメール






カートに入れた商品を購入できるようにチェックアウト機能を実装します。
ここでは、以下の機能を実装します。

  1. フォームからの値を取得して新しいOrderモデルオブジェクトに格納する。
  2. カートに入っているLineItemをその注文に追加する。
  3. 注文のデータが正当かどうかを検証して保存する。注文データが不正である場合はエラーメッセージを表示してユーザに修正を求める。
  4. 注文が正常に保存されたらカートを削除し、カタログページを再表示して注文手続きが完了したところをメッセージで通知する。


まずは「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テーブルに注文が追加されているのが確認できるはずです。

Orderテーブル確認
Orderテーブル確認




21 | アクションケーブル(ActionCable)】 << 【ホーム】 >> 【23 | Gメール





↓↓クリックして頂けると励みになります。