Formオブジェクトを用いて作成したデータを、編集・更新する方法

#要点
+ 初心者がアプリを作るときの参考に
+ formオブジェクトを用いた編集・更新機能は複雑なので詳しく知りたいorおさらいしたい
+ 下記のエラーを解消する方法

```
param is missing or the value is empty: 'Formオブジェクト名'
```

#はじめに

メルカリのようなフリマアプリを作成中で、Formオブジェクトを用いて商品にタグ付けして編集・更新（edit・update）する機能を実装まで行いました
各モデルとコントローラーは以下のようになります

+ Item/商品
+ Tag/タグ
+ TagItemRelation/商品とタグの中間テーブル
+ TagsItem/ ItemとTagを同時に保存するためのFormオブジェクト

```ruby:/app/model/item.rb
class Item < ApplicationRecord
  has_many :tag_item_relations, foreign_key: :item_id, dependent: :destroy
  has_many :tags, through: :tag_item_relations
end
```

```ruby:/app/model/tag.rb
class Tag < ApplicationRecord
  has_many :tag_item_relations
  has_many :items, through: :tag_item_relations
  validates :tag_name, uniqueness: true
end
```


```ruby:/app/model/tag_item_relation.rb
class TagItemRelation < ApplicationRecord
  belongs_to :item
  belongs_to :tag
end
```

```ruby:/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  def save
    item = Item.create(item_name: item_name)
    tag = Tag.where(tag_name: tag_name).first_or_initialize
    tag.save
    TagItemRelation.create(item_id: item.id, tag_id: tag.id)
  end
end
```

コントローラー

```ruby:/app/controller/items_controller.rb
class ItemsController < ApplicationController
   def index
     @items = Item.all.order('created_at ASC')
   end

   def edit
    @item = current_user.items.find(params[:id])
    @tegs_item = TagsItem.new(item: @item)
   end
 
   def update
    @item = current_user.items.find(params[:id])
    @tags_item = TagsItem.new(update_items_params, item: @item)
     if @tags_item.valid?
       @tags_item.save
       redirect_to root_path
     else
       render :edit
     end
   end

  private

  def update_items_params
    params.require(:item).permit(
      :item_name,
      :tag_name
    )
  end
end
```

しかし実際に商品を編集・更新をしてみると、、、

```
param is missing or the value is empty: 'tags_item'
```

のエラーが出てしまい、商品の更新ができませんでした


#調べたこと
エラー内容を読んでみると生成したFormオブジェクトの__'tag_item_relation'__が空になっているとのこと
そして、binding.pryなどを使って調べてみると、そもそもこの記述では__'tags_item'__が__編集・更新では機能していない__ことが分かります
さらに原因を調べてみると新規投稿（new・create）と編集更新(edit・update)の機能を__仕分け__していなかったことだと分かりました



# Formオブジェクトで編集更新
今回のエラーの原因はFormオブジェクトに記載したsaveが新規投稿と編集更新で仕分けされていなかったことだと分かりました


```ruby:/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  def save
    item = Item.create(item_name: item_name)  #ここがcreateアクションのみ
    tag = Tag.where(tag_name: tag_name).first_or_initialize
    tag.save
    TagItemRelation.create(item_id: item.id, tag_id: tag.id)
  end
end
```

この記述を

```ruby:/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  # itemがすでに保存されているものか、新規のものかで、PUTとPATCHを分ける
  delegate :persisted?, to: :item


  # initializeでFormオブジェクトの値を初期化し、更新の際はdefault_attributesを呼び出す設定
  def initialize(attributes = nil, item: Item.new)
    @item = item
    attributes ||= default_attributes
    super(attributes)
  end

  def save
    return if invalid?

    ActiveRecord::Base.transaction do
      # mapメソッドを使いsplit_tag_namesをtagの情報に変換
      tags = split_tag_names.map { |tag_name| Tag.find_or_create_by!(tag_name: tag_name) }
      item.update!(item_name: item_name, tags: tags)
    end
  rescue ActiveRecord::RecordInvalid
    false
  end

  #  formを飛ばす場所を（#createか#updateか）を判別して、切り替えている
  def to_model
    item
  end

  private

  attr_reader :item

  def default_attributes
    {
      item_name: item.item_name,
      tag_name: item.tags.pluck(:tag_name).join(',')
    }
  end

  def split_tag_names
    tag_name.split(',')
  end
end
```
に変更することで無事にタグ付け機能の編集更新を行うことができました



#参考にしたサイト
[Railsのデザインパターン: Formオブジェクト](https://product-development.io/posts/rails-design-pattern-form-objects)

[<rails>formオブジェクトパターンで、タグ付け機能を実装](https://tomo-bb-aki0117115.hatenablog.com/entry/2020/10/29/232822)

#関連記事
[Formオブジェクトを用いて作成したデータを、特定のデータのみ削除する方法](https://qiita.com/ogamw/items/4d656c7bf67e0e5f57fa)←(前回記事)

[railsでタグ機能を実装する](https://qiita.com/you8/items/b2394104c6f9865f5d46)