PF作成においてFormObjectを使用した際に、編集機能の実装に苦戦し時間を要したため覚え書きとして投稿させていただきます。
form_withにおける登録、更新の切り替え
form_with model: インスタンス変数 do |f|
上記においてform_with
は渡されたインスタンス変数のメソッドpersisted?
の返り値によってリクエストをPOSTもしくはPATCHに切り替えています。
persisted?
メソッドはレシーバーがDBに保存されている場合はtrue,保存されていない場合はfalseを返します。
urlオプションを渡さない場合、フォームの送信先urlは同様に渡されたインスタンス変数のメソッドto_model
の返り値によって決定されます。
使用例
今回、下記ER図におけるPurchaseモデルのレコードを保存するにあたり、ShopとBeanの選択をセレクトボックスにしてしまうとレコードの量次第では選択肢が膨大になりUIとして不適格だと判断したため、Stimulus-autocompleteとテキスト入力を組み合わせて利用するため、Formobjectを利用する選択を取りました。
formobject内でpersisted?,to_modelに対応する
1. 初期化処理をオーバーライドする
formobject
def initialize(attributes = nil, purchase: Purchase.new)
@purchase = purchase
if @purchase.persisted? && attributes
new_attributes = default_attributes.merge(attributes)
super(new_attributes)
elsif @purchase.persisted?
new_attributes = default_attributes
super(new_attributes)
else
super(attributes)
end
end
def default_attributes
{
shop_name: @purchase.shop.name,
shop_place_id: @purchase.shop.place_id,
bean_name: @purchase.bean.name,
bean_id: @purchase.bean_id,
purchase_at: @purchase.purchase_at,
store_roast_option: @purchase.store_roast_option,
store_grind_option: @purchase.store_grind_option,
user_id: @purchase.user_id
}
end
前項で解説したpersisted?
を利用し、DBへの保存の有無と引数の有無により処理を分岐しsuper
でオーバーライド元のActiveModel::Model
のinitialize
へ処理を投げています
controller
class PurchasesController < ApplicationController
before_action :set_purchase, only: %i[edit update]
before_action :set_purchase_form, only: %i[edit]
def new
@purchase_form = PurchaseForm.new
end
def create
@purchase_form = PurchaseForm.new(purchase_form_params)
if @purchase_form.save
redirect_to mypage_purchases_path, success: t('defaults.message.created', item: Purchase.model_name.human)
else
render :new, status: :unprocessable_entity
end
end
def edit; end
def update
@purchase_form = PurchaseForm.new(purchase_form_params, purchase: @purchase)
if @purchase_form.update
redirect_to mypage_purchases_path, success: t('defaults.message.updated', item: Purchase.model_name.human)
else
render :edit, status: :unprocessable_entity
end
end
private
def purchase_form_params
params.require(:purchase).permit(:shop_name, :shop_place_id, :bean_name, :bean_id,
:store_roast_option, :store_grind_option, :purchase_at)
.merge(user_id: current_user.id)
end
def set_purchase
@purchase = current_user.purchases.find(params[:id])
end
def set_purchase_form
@purchase_form = PurchaseForm.new(purchase: @purchase)
end
end
コントローラー側はモデルと同様の実装で使用できます。
2. delegateを利用してto_model,persisted?を委譲する
delegate :persisted?, :to_model, to: :@purchase
上記2点の対応によってフォームは@purchase
がDBに保存されているか否かによってリクエストが切り替わります。
参考
Railsで #update のできるFormObjectを作る
form objectでupdate機能を実装した話
【Rails】find, updateを備えたフォームオブジェクト