#要点
- 初心者がアプリを作るときの参考に
- formオブジェクトを用いた編集・更新機能は複雑なので詳しく知りたいorおさらいしたい
- 下記のエラーを解消する方法
param is missing or the value is empty: 'Formオブジェクト名'
#はじめに
メルカリのようなフリマアプリを作成中で、Formオブジェクトを用いて商品にタグ付けして編集・更新(edit・update)する機能を実装まで行いました
各モデルとコントローラーは以下のようになります
- Item/商品
- Tag/タグ
- TagItemRelation/商品とタグの中間テーブル
- TagsItem/ ItemとTagを同時に保存するためのFormオブジェクト
class Item < ApplicationRecord
has_many :tag_item_relations, foreign_key: :item_id, dependent: :destroy
has_many :tags, through: :tag_item_relations
end
class Tag < ApplicationRecord
has_many :tag_item_relations
has_many :items, through: :tag_item_relations
validates :tag_name, uniqueness: true
end
class TagItemRelation < ApplicationRecord
belongs_to :item
belongs_to :tag
end
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
コントローラー
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が新規投稿と編集更新で仕分けされていなかったことだと分かりました
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
この記述を
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オブジェクト
#関連記事
Formオブジェクトを用いて作成したデータを、特定のデータのみ削除する方法←(前回記事)