3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FormObjectを編集に対応させる

Last updated at Posted at 2024-04-25

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を利用する選択を取りました。
Image from Gyazo

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::Modelinitializeへ処理を投げています

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を備えたフォームオブジェクト

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?