FormObjectを編集に対応させる

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](https://i.gyazo.com/d72b59cc91045e8552a7425372d2dff4.png)](https://gyazo.com/d72b59cc91045e8552a7425372d2dff4)

# 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を作る](https://tech.actindi.net/2021/08/15/001308)
[form objectでupdate機能を実装した話](https://qiita.com/sazumy/items/5ff5f1e653d802929f0b)
[【Rails】find, updateを備えたフォームオブジェクト](https://blog.aiandrox.com/posts/tech/2022/11/15/)