🍺

Rails カート内の非同期通信

2023/05/28に公開

はじめに

プログラミング学習を始めて2ヶ月の初学者です🔰
本日チーム開発の動画撮影まで無事終わりました🏋🏻
途中でメンバーが減り3人になってしまいましたが、
みんなでスピード感を持って作業に取り組み完成できました!

他のチームの完成動画を見ていると、自分では出てこなかったアイディアが
たくさんあってめちゃ面白いし勉強になりますね🥹

今日は、チームメンバーの方がカートの非同期実装に成功していて、
やり方を軽く教わったので、メモを残しておこうと思います!

カート内の非同期通信

こちらの画面で数量を変更すると、
非同期で小計と合計金額が変わります!👀👏

View

変更点としては、変更ボタンをコメントアウトし、
<tr id="cart_item_<%= cart_item.id %>">
こちらの記述を追記。id属性をカートアイテムのIDに設定しています。

これにより、各カートアイテムの行を一意に識別できます!
これがないと、一つの小計が変わった時に、全ての小計が変わってしまいます。

下記のコードで、関係ないところが赤くなってしまっていますが気にしないでください!

cart_itemsのindex
views/public/cart_items/index.html.erb
<div class="container mt-5 d-flex-column space-around">
  <div class="row row-content">
   <% if @cart_items.blank? %>
    <div class="col-md-8 text-center">
     <h3>カート内に商品はございません</h3>
    </div>
   <% else %>
     <div class="col-md-10">
      <div class="row justify-content-center head-items mb-4">
        <h4 class="col-md-4 text-center headline">ショッピングカート</h4>
      </div>
      <div class="row justify-content-end">
        <div class="col-md-2 mb-2">
         <%= link_to 'カートを空にする',clear_cart_items_path, method: :delete, class: "btn btn-danger", data: { confirm: "本当に空にしますか?" } %>
        </div>
      </div>
      <table class="table table-hover table-inverse table-bordered custom-table", style="border-color: #3C2A21;">
        <thead>
          <tr align=center >
            <th scope="col" style="width: 40%">商品名</th>
            <th scope="col" style="width: 10%">単価(税込)</th>
            <th scope="col" style="width: 20%">数量</th>
            <th scope="col" style="width: 10%">小計</th>
            <th scope="col" style="width: 10%"></th>
          </tr>
        </thead>
        <tbody>
+          <% @cart_items.each do |cart_item| %>
            <tr id="cart_item_<%= cart_item.id %>">
              <td class="align-middle"><%= image_tag cart_item.item.get_image(100, 100), class: "p-2 image" %> <%= cart_item.item.name %></td>
              <td class="align-middle text-center"><%= cart_item.item.with_tax_price.to_s(:delimited) %>円</td>
              <td id="select-form" class="align-middle">
               <%= form_with(model: cart_item, url: cart_item_path(cart_item), method: :patch, local: false, class: "text-center") do |form| %>
                 <%= form.hidden_field :customer_id, :value => current_customer.id %>
                 <%= form.hidden_field :item_id, :value => cart_item.item.id %>
                 <%= form.select :quantity, *[1..50],{}, class: "form-select-sm" %>
                 <%#= form.submit "変更", class: "btn change_button", style: "background-color: #D5CEA3;" %>
               <% end %>
              </td>
              <td class="align-middle subtotal-area text-center">
                <%= render 'subtotal', cart_item: cart_item %>
              </td>
              <td class="align-middle text-center"><%= link_to '削除', cart_item_path(cart_item), method: :delete, class: "btn btn-danger", data: { confirm: "本当に削除しますか?" }   %></td>
            </tr>
          <% end %>
        </tbody>
      </table>
     </div>
    </div>
    <div class="row row-content-middle mt-3">
      <div class="col-md-4">
       <%= link_to '買い物を続ける', root_path, class: "btn", style: "background-color: #1A120B; color: #E5E5CB;" %>
      </div>
      <div class="col-md-2">
       <table class="table table-hover table-inverse table-bordered", style="border-color: #3C2A21;">
         <tbody>
           <tr align="center">
             <th style="background-color: rgba(60, 42, 33, 0.5);">合計金額</th>
             <td class="text-center total-area">
               <%= render 'total', total_amount: @total_amount %>
              </td>
           </tr>
         </tbody>
       </table>
      </div>
    </div>
    <div class="row mt-5 row-content">
     <div class="col-md-3 text-center">
       <%= link_to '情報入力へ進む', new_order_path, class: "btn", style: "background-color: #1A120B; color: #E5E5CB;" %>
     </div>
   <% end %>
  </div>
</div>

+<!--下記は変更ボタンを押さなくてもselectで個数を選択すると自動で更新が発火されるコードです-->
+<script>
+  $(document).on("change", "#select-form select", function() {
+    let form = $(this).closest("form");
+    let url = form.attr("action");
+    let method = form.attr("method");
+    let data = form.serialize();
+
+    $.ajax({
+      url: url,
+      method: method,
+      data: data,
+      success: function(response) {
+        let cartItemRow = form.closest("tr#cart_item");
+        cartItemRow.find(".subtotal-area").html(response);
+      },
+      error: function(xhr) {
+        console.log(xhr.responseText);
+      }
+    });
+  });
+</script>

scriptの中の記述はJavaScriptコードで、まだ勉強不足ではありますが、
GPT先生に聞いて意味をまとめてみました。

$(document).on("change", "#select-form select", function() { ... });
select-formクラスを持つセレクトボックスの変更イベントを監視しています。
つまり、数量が変更されたときにこのイベントが発火します。

let form = $(this).closest("form");
変更されたセレクトボックスに最も近いform要素を取得しています。

let url = form.attr("action");
フォームのaction属性を取得し、リクエストの送信先URLとして使用します。

let method = form.attr("method");
フォームのmethod属性を取得し、リクエストのHTTPメソッドとして使用します。

let data = form.serialize();
フォームのデータをシリアライズし、リクエストのデータとして使用します。

$.ajax({ ... });
Ajaxリクエストを送信します。指定されたURL、メソッド、データでリクエストを行います。

success: function(response) { ... }
リクエストが成功した場合のコールバック関数です。レスポンスデータを受け取り、変更されたカートアイテムの行の小計を更新します。

error: function(xhr) { ... }
リクエストがエラーとなった場合のコールバック関数です。エラーメッセージをコンソールに出力します。

このJavaScriptコードにより、数量の変更が行われるとAjaxリクエストが発生し、
サーバーからのレスポンスに基づいてカートアイテムの小計が動的に更新されます!!

JSファイル

public/cart_items/update.js.erb
$("#cart_item_<%= @cart_item.id %> .subtotal-area").html("<%= j(render 'subtotal', cart_item: @cart_item) %>")
$(".total-area").html("<%= j(render 'total', total_amount: @total_amount) %>")

Controller

コントローラーのupdateアクション

public/cart_items/cart_items_controller.rb
class Public::CartItemsController < ApplicationController
  before_action :authenticate_customer!
  def update
    @cart_item = current_customer.cart_items.find(params[:id])
    @cart_item.update(cart_item_params)
    @cart_items = current_customer.cart_items.all
    @total_amount = @cart_items.inject(0) { |sum, item| sum + item.subtotal }
  end
  private

  def cart_item_params
    params.require(:cart_item).permit(:customer_id, :item_id, :quantity)
  end
end

記事を書いてメモを残してみたものの、あまり理解できていないです🥲
JavaScriptについてはこれからもっと学んでいきたいと思っています!
また後で見返した時に、理解できているように、勉強がんばります💪🏻

パワー🏋🏻

Discussion