🤍

Rails sessionについて

2023/05/27に公開

リロードするとエラーになる問題

注文情報入力画面

注文情報確認画面

この画面をリロードすると、エラーになってしまう。

ターボリンクスを全消ししたところ、エラーが起きなくなったという情報を手に入れました。
(ターボリンクスはページ遷移時にページ全体をリフレッシュしないため、POSTリクエストを行った後にリロードすると、前回のPOSTリクエストが存在しないと認識され、エラーが発生することがある。)

下記の手順でやってみたが変化がなく、、
https://qiita.com/Cheekyfunkymonkey/items/216bf7426493e6213927

違う方法はないか?と探しているところ、下記の記事からヒントを得て、ChatGPTに頼りながら解決できました。

問題は、redirectしてしまうとPOSTの値を保持できなくなってしまうこと。
解決法として、一番簡単なのは値をsessionに保持しておく方法らしい。

https://qiita.com/yuyasat/items/49e3296f3c64fccc7811

このエラーは、Public::OrdersController#confirmアクションで:orderが空であることを示しています。(ストロングパラメーターを使用してパラメーターの検証を行っているため)

ブラウザでページをリロードすると、POSTリクエストで送信された :order パラメーターは再送されません。
ページのリロードは基本的にGETリクエストを行います。
そのため、POSTリクエストで送信された :order パラメーターが存在しなくなり、その結果としてエラーが発生しています。

この問題を解決するために、セッションを使用して :order パラメーターを一時的に保管する方法を選びました。

sessionについてわかりやすい記事

https://qiita.com/ozackiee/items/4ee774c81b2a0c571c05
https://www.slideshare.net/carotene4035/sessionrailscookie-store
https://tyfkda.github.io/blog/2020/09/01/rails6-session-cookie.html

Railsではsessionメソッドはデフォルトで利用可能
Cookieが持っているsession情報を複合化して「誰がどういう状態か」を把握している。

そもそもセッションとは?
セッションとは一連の通信開始から終了までの処理を意味していて、
Webアプリケーションではユーザーのログイン情報や状態の保持に利用されます。

Webの通信にはHTTP(Hypertext Transfer Protocol)が使用されます。
ご存じのようにHTTPはステートレスなプロトコルです。
ステートレスとは、状態(情報)を保持できないということ。

なので、状態の保持にセッションを使う必要がある。
セッションはWebサーバーが発行し、
セッションはCookieに保存され、ブラウザにCookieは保存される。

Cookie(クッキー)とは「名前=値」の形でブラウザ上にアクセス情報やユーザー情報が保存される仕組み。

https://office54.net/iot/programming/http-stateless-session

セッションを使ってパラメーターを一時的に保存する方法は次の通りです。

public/orders_controller
  def confirm
+  if params[:order]
    @order = Order.new(order_params)
    @order.customer_id = current_customer.id
    @cart_items = current_customer.cart_items
    @total_amount = @cart_items.inject(0) { |sum, item| sum + item.subtotal }
    @order.postage = 800
    @order_total_amount = @total_amount + @order.postage.to_i

    if params[:order][:select_address] == "0"
      @order.shipping_post_code = current_customer.post_code
      @order.shipping_address = current_customer.address
      @order.shipping_name = current_customer.last_name + current_customer.first_name
    elsif params[:order][:select_address] == "1"
      if ShippingAddress.exists?(id: params[:order][:address_id])
        @address = ShippingAddress.find(params[:order][:address_id])
        @order.shipping_name = @address.name
        @order.shipping_post_code = @address.postal_code
        @order.shipping_address = @address.address
      else
        flash[:notice] = "配送先情報がありません"
        render 'new'
      end
    elsif params[:order][:select_address] == "2"
      @order.shipping_name = params[:order][:shipping_name]
      @order.shipping_post_code = params[:order][:shipping_post_code]
      @order.shipping_address = params[:order][:shipping_address]
    else
      render 'new'
    end
      @address = "〒" + @order.shipping_post_code + @order.shipping_address
+     session[:order] = @order.attributes
  end
+   if session[:order]
+    @order = Order.new(session[:order])
+    @cart_items = current_customer.cart_items
+    @total_amount = @cart_items.inject(0) { |sum, item| sum + item.subtotal }
+    @order.postage = 800
+    @order_total_amount = @total_amount + @order.postage.to_i
+    @address = "〒" + @order.shipping_post_code + @order.shipping_address
+  else
+    @order = Order.new
+  end
end

コードをもっと短略化できる気がしますが、
このまま解説いきます!

〜解説〜

if params[:order]
リクエストパラメータの中にorderキーが存在するか確認。
もし存在すれば、その値はフォームから送信された注文データであり、そのデータを使用して注文情報を初期化します。

@order = Order.new(order_params)
新たな注文オブジェクトを作成しています。

@order.customer_id = current_customer.id
現在の顧客のIDを注文オブジェクトの顧客IDとして設定。

@cart_items = current_customer.cart_items
現在の顧客がカートに追加したアイテムを取得。

@total_amount = @cart_items.inject(0) { |sum, item| sum + item.subtotal }
カート内のアイテムすべての小計を合計して、注文の合計金額を計算。

@order.postage = 800
注文の送料を設定

@order_total_amount = @total_amount + @order.postage.to_i
注文の合計金額と送料を加算して、注文の総額を計算。

if params[:order][:select_address] == "0"からのコードは、
注文の配送先を選択するロジックを処理しています。

具体的には、送信されたフォームデータに基づいて、注文の配送先が現在の顧客の住所、保存されている別の配送先、またはフォームから直接入力された新しい配送先、のいずれであるかを決定します。
"0"の場合は現在の顧客の情報、"1"の場合は指定したIDの配送先情報、"2"の場合はパラメータに直接指定された情報を使います。

フォームから送信されるselect_addressの値によって選択を分岐し、
どの条件にも該当しない場合、注文フォームを再表示します。

@address = "〒" + @order.shipping_post_code + @order.shipping_address
注文の配送先住所を整形。

session[:order] = @order.attributes
最後に、作成したOrderオブジェクトの情報をセッションに保存します。
これにより、ユーザーが他のページに遷移しても注文情報が維持されます。

if session[:order]
セッションにorderの情報がある場合(つまり、前回のリクエストで注文情報が設定されていた場合)、その情報を元にOrderオブジェクトを再度作成し、顧客のカート内のアイテムを取得します。存在しない場合は新しい(空の)Orderオブジェクトを作成します。

ページリロードがあった場合(つまり params[:order] が存在しない場合)、セッションから @order のデータを取り出します。これにより、リロード後も @orderが利用可能となります🙆🏻‍♀️


なるほど、まだ深くは理解できていない&上の記述が正しい記述か自信はないけど、
今まで意識してなかったsessionというものについて知れてよかった!

こうやってどんどん少しずつでも知識を積み重ねていきたい!

Discussion