バリデーションでエラーが出た後、前の情報を残したい

バリデーションでエラーが出た後、前の情報を残したい(formオブジェクトパタンを用いた購入機能を実装中)

  • 質問の箇所

    フリマアプリ   formオブジェクトパタンを用いた購入機能を実装中。

  • 質問の内容

  • 問題に関する情報

class PurchasesController < ApplicationController
  before_action :authenticate_user!, except: :index
  def index
    @item = Item.find(params[:item_id])
    @purchase = Purchase.new
    @address = @purchase.build_address
    @purchase_address = PurchaseAddress.new
    # @purchase_address = @item.build_purchase_address
  end

  def new
    @purchase_address = PurchaseAddress.new
    # @purchase_address = @item.build_purchase_address
  end
  
  def create
    @item = Item.find(params[:item_id])
    @purchase_address = PurchaseAddress.new(purchase_params)
    if @purchase_address.valid?
      @purchase_address.save
      redirect_to root_path
    else
      render :index
    end
  #   item = Item.find(params[:item_id])
  #   @purchase = item.create_purchase!(user_id: current_user.id)
  #   @purchase.create_address!(address_params)
  #   redirect_to root_path
  end

  private

  # def purchase_params 
  #   params.merge(user_id: current_user.id, item_id: params[:item_id])
  # end 

  # def address_params
    # params.permit(:postal_code, :prefecture_id, :city, :house_number, :phone_number, :building_name).merge(purchase_id: @purchase.id)  
  # end

  def purchase_params
    params.permit(:postal_code, :prefecture_id, :city, :house_number, :phone_number, :building_name).merge(user_id: current_user.id, item_id: params[:item_id])
  end
end
<%= render "shared/second-header"%>
<div class='transaction-contents'>
  <div class='transaction-main'>
    <h1 class='transaction-title-text'>
      購入内容の確認
    </h1>
    <%# 購入内容の表示 %>
    <div class='buy-item-info'>
      <%= image_tag @item.image, class: 'buy-item-img' %>
      <div class='buy-item-right-content'>
        <h2 class='buy-item-text'>
          <%= @item.name %>
        </h2>
        <div class='buy-item-price'>
          <p class='item-price-text'>¥<%= @item.price %></p>
          <p class='item-price-sub-text'><%= @item.shipping.shipping_id %></p>
        </div>
      </div>
    </div>
    <%# /購入内容の表示 %>
    <%# 支払額の表示 %>
    <div class='item-payment'>
      <h1 class='item-payment-title'>
        支払金額
      </h1>
      <p class='item-payment-price'>
        ¥<%= @item.price %>
      </p>
    </div>
    <%# /支払額の表示 %>
    <%= form_with url: item_purchases_path, method: [@purchase_address,@purchase,@address], id: 'charge-form', class: 'transaction-form-wrap', local: true do |f| %>
    <%= render 'shared/error_messages', model: @purchase_address %>
    <div class='shipping-address-form'>
      <h1 class='info-input-haedline'>
        配送先入力
      </h1>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">郵便番号</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :postal_code, class:"input-default", id:"postal-code", placeholder:"例)123-4567", maxlength:"8" %>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">都道府県</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.collection_select(:prefecture_id, Prefecture.all, :id, :prefecture_id, {}, {class:"select-box", id:"prefecture"}) %>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">市区町村</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :city, class:"input-default", id:"city", placeholder:"例)横浜市緑区"%>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">番地</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :house_number, class:"input-default", id:"addresses", placeholder:"例)青山1-1-1"%>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">建物名</label>
          <span class="form-any">任意</span>
        </div>
        <%= f.text_field :building_name, class:"input-default", id:"building", placeholder:"例)柳ビル103"%>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">電話番号</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :phone_number, class:"input-default", id:"phone-number", placeholder:"例)09012345678",maxlength:"11"%>
      </div>
    </div>
    <%# /配送先の入力 %>
    <div class='buy-btn'>
      <%= f.submit "購入" ,class:"buy-red-btn", id:"button" %>
    </div>
    <%# <% end %> %>
  </div>
</div>
<% end %>
<%= render "shared/second-footer"%>
class PurchaseAddress
  include ActiveModel::Model
  attr_accessor :user_id, :item_id, :postal_code, :city, :house_number, :phone_number, :building_name, :prefecture_id

  with_options presence: true do
    validates :postal_code, format: {with: /\A[0-9]{3}-[0-9]{4}\z/, message: "is invalid. Include hyphen(-)", allow_blank: true }
    validates :prefecture_id, numericality: {other_than: 1, message: "can't be blank"}
    validates :city
    validates :house_number
    validates :phone_number, format: { with: /\A0[0-9]+\z/, message: 'number is invalid. Include half-width numbers' , allow_blank: true } 
    validates :user_id
    validates :item_id
  end

  def save
    purchase = Purchase.new
    address = @purchase.build_address
    purchase = Purchase.create(user_id: user_id, item_id: item_id)
    Address.create(postal_code: postal_code, city: city, house_number: house_number, phone_number: phone_number, building_name: building_name, prefecture_id: prefecture_id, purchase_id: purchase.id)
  end

end
  • 問題に関するターミナルやコンソールの情報:

From: /Users/user/projects/furima-35933/app/controllers/purchases_controller.rb:19 PurchasesController#create:

    16: def create
    17:   @item = Item.find(params[:item_id])
    18:   @purchase_address = PurchaseAddress.new(purchase_params)
 => 19:   binding.pry
    20:   if @purchase_address.valid?
    21:     @purchase_address.save
    22:     redirect_to root_path
    23:   else
    24:     render :index
    25:   end
    26: #   item = Item.find(params[:item_id])
    27: #   @purchase = item.create_purchase!(user_id: current_user.id)
    28: #   @purchase.create_address!(address_params)
    29: #   redirect_to root_path
    30: end

[1] pry(#<PurchasesController>)> @purchase_address
=> #<PurchaseAddress:0x00007ffd49fb2450
 @building_name="",
 @city="日高市",
 @house_number="森山1-5-77",
 @item_id="31",
 @phone_number="09084842343",
 @postal_code="213-3456",
 @prefecture_id="3",
 @user_id=33>
[2] pry(#<PurchasesController>)> @purchase_address.valid?
=> true
[3] pry(#<PurchasesController>)> exit
  • 解決したいこと

     formオブジェクトパタンを用いた購入機能を実装中。 バリデーションでエラーが出た後、前の情報を残したい

  • 調べた内容とそこから立てた仮説

     カリキュラムの フォームオブジェクトパタンの定義方法を参考に購入機能を実装してみた コントローラでpurchase_addressのインスタンスを生成し、form_with に代入ができたと思いますが、form_wiht で入力したデータをコントローラで送ってきた値をバリデーションをかけ、入力問題がある時、エラーメッセージが出ていたが、入力した内容は消え、form_wihtにはコントローラで生成したpurchase_addressのインスタンスを指定したにも関わらず、残せないのは、今回の問題点ではと考えております、そのため、form_withに新たにpurchaseコントローラでインスタンス@purchase,@address を生成し、指定したが、解決できなかった、なので問題は別のところにあるのではと考えておりますが.

    今回においては、上記の@purchaseと@addressは、form_withの実装で特別使用することがないインスタンス変数になる め,@purchase_addressのみを使用して実装ができる記述で進めていきますので、今回は不要となります。

はい、ダメでした!

  • 検証
    • 「@purchase_address」というインスタンス変数に正しく値が入っているかを確認するところ
[1] pry(#<PurchasesController>)> @purchase_address
=> #<PurchaseAddress:0x00007ffd49fb2450
 @building_name="",
 @city="日高市",
 @house_number="森山1-5-77",
 @item_id="31",
 @phone_number="09084842343",
 @postal_code="213-3456",
 @prefecture_id="3",
 @user_id=33>
[2] pry(#<PurchasesController>)> @purchase_address.valid?
=> true
  • ちゃんと入ってることがわかりましたが、、、 入力したデータを購入ページに反映させたい場合は、form_with内にある「@purchase_address」に含まれる値を受け取り、ビューに既存のデータとして反映させます。つまり、form_with の記述は問題があるかを確認する必要あるここでもう一回form_with 見直します。
<%= form_with  method: @purchase_address,  ←(キーが「method:」になっています)

それでform_withの記述を修正:

<%= form_with url: item_purchases_path, model: @purchase_address, id: 'charge-form', class: 'transaction-form-wrap', local: true do |f| %>
  • そして、以下の手順にしたがって、

rails sのし直し。 余計なエラーが出ることを防ぐため、一度最新の状態にリセットさせます。

localhost: 3000のトップページから購入ページに移る。 こちらも上記の理由と同じです。確実に最新の状態でページを始められる状態にします。

③購入ページで「購入ボタン」を押した後に、入力情報が残るかどうかを確認。

ターミナル上:

From: /Users/user/projects/furima-35933/app/controllers/purchases_controller.rb:15 PurchasesController#create:

    12: def create
    13:   @item = Item.find(params[:item_id])
    14:   @purchase_address = PurchaseAddress.new(purchase_params)
 => 15:   binding.pry
    16:   if @purchase_address.valid?
    17:     @purchase_address.save
    18:     redirect_to root_path
    19:   else
    20:     render :index
    21:   end
    22: #   item = Item.find(params[:item_id])
    23: #   @purchase = item.create_purchase!(user_id: current_user.id)
    24: #   @purchase.create_address!(address_params)
    25: #   redirect_to root_path
    26: end

[1] pry(#<PurchasesController>)> @purchase_address
=> #<PurchaseAddress:0x00007ff483a4c750 @item_id="31", @user_id=33>
[2] pry(#<PurchasesController>)> @purchase_address
=> #<PurchaseAddress:0x00007ff483a4c750 @item_id="31", @user_id=33>
[3] pry(#<PurchasesController>)> @purchase_address.valid? 
=> false
[4] pry(#<PurchasesController>)> @purchase_address.errors.full_messages
=> ["Postal code can't be blank", "Prefecture can't be blank", "Prefecture can't be blank", "City can't be blank", "House number can't be blank", "Phone number can't be blank"]
[5] pry(#<PurchasesController>)> exit
  • また、新たな問題発見!!!!

    • どうやら、インスタンス @purchase_addressに@item_id="31", @user_id=33 の情報しかない、なぜ?ストロングパラメータに問題でも? 
 def purchase_params
    params.permit(:postal_code, :prefecture_id, :city, :house_number, :phone_number, :building_name).merge(user_id: current_user.id, item_id: params[:item_id])
permit(:postal_code, :prefecture_id, :city, :house_number, :phone_number, :building_name)

の情報が受け取らなかった!!!ていうことは、、、指定しなかったのでは、、、

def purchase_params
    params.require(:purchase_address).permit(:postal_code, :prefecture_id, :city, :house_number, :phone_number, :building_name).merge(user_id: current_user.id, item_id: params[:item_id])
  end

require(:purchase_address)追記し、もう一回試したら、、、、

したら、無事解決できました!!!

  • まとめ:from_withの記述が間違ったことは今回の原因でした!!!とても初歩的なミス!!!もう二度と同じ間違いを犯さないようにメモとして残しときます。

最後:問題解決に手伝って頂いたメンターの方に感謝!!

(テックキャンプ 102期 受講生)

追記:クレジットカード情報の項目は省いております。