はじめに
実装したい内容としては、タイトルの通りです。
考えてみれば辺り前だなということだったのですが、アプリケーションを作成中に少しハマったので、忘れない用に書き残します。
Railsは便利ですが、故に最初は詳細な部分が分かりづらいことがあると
環境
- Ruby 2.7.2
- Rails 6.0.3.4
要件
要件を再度整理します。
- 親子関係にある複数のモデルを一度にフォームから登録したい。
- 子のモデルの方は、チェックボックスで複数選択をさせて登録させたい。
モデル関係
※ 記事の説明に必要な最低限の情報のみ記載
shopモデル
カラム | 型 | 備考 |
---|---|---|
id | integer | pk |
shop_name | string |
tasteモデル
ショップで扱う系統(アメカジ、ストリート、カジュアル etc..)を格納するマスタデータ
カラム | 型 | 備考 |
---|---|---|
id | integer | pk |
style | string |
shop_tasteモデル
shop
モデルとtaste
モデルの情報を格納する中間モデル
カラム | 型 | 備考 |
---|---|---|
id | integer | pk |
shop_id | integer | fk (参照先:shopモデルのid ) |
taste_id | integer | fk (参照先:tasteモデルのid ) |
関連付け
shop.rb
内で、同一のフォームで登録したいモデルに対して、accepts_nested_attributes_for
を指定する。
- shopモデル
class Shop < ApplicationRecord
has_many :shop_tastes, dependent: :destroy
has_many :tastes, through: :shop_tastes
accepts_nested_attributes_for :shop_tastes, allow_destroy: true
end
- tasteモデル
class Taste < ApplicationRecord
has_many :shop_tastes, dependent: :destroy
has_many :shops, through: :shop_tastes
end
- shop_tasteモデル
class ShopStyle < ApplicationRecord
belongs_to :shop
belongs_to :taste
end
controllerとview
以下が今回ハマった部分のコードになります。
def shop_params
params.require(:shop).permit( :shop_name, taste_attributes[taste_ids:[]])
end
<%= form_with model: @shop, local: true do |f| %>
<% f.fields_for :shop_tastes do |fc| %>
<%= fc.collection_check_boxes :taste_ids, Taste.all, :id, :style, checked: @shop.taste_ids ,include_hidden: false do |b| %>
<%= b.check_box %>
<%= b.text %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
ハマった理由とポイント
上記のコードで、フォームに値を入力して、送信を行うと unkown attributes taste_ids for ...
と、そんな属性はない、という旨のエラーメッセージが出力されてしまいました。
自分では、小モデルの複数登録の場合collection_check_boxes
と fields_for
を基本的にセットで使用すると考えていました。
しかしcollection_check_boxes
自体パラメータを送信する時に配列の形式で送信を行うため、XXX_ids
のようなパラメータ名を指定する必要があります。
API docでfields_for
を見ると、以下の用に記述がありました。
Like form_for, it yields a FormBuilder object associated with a particular model object to a block, and within the block allows methods to be called on the builder to generate fields associated with the model object.
ざっくり日本語訳すると、以下のような内容です。
form_for と同様に、特定のモデルオブジェクトに関連付けられた FormBuilder オブジェクトをブロックに生成し、ブロック内でビルダーにメソッドを呼び出してモデルオブジェクトに関連付けられたフィールドを生成することができます。
確かに、shop_taste
モデルにはtaste_ids
というフィールドは存在しないため、エラーになるのは当たり前ですね。。。
自分の中で、親子関係にある複数モデルを一気に登録する場合、fields_for
は必須だと勘違いしていたのですがそうではありませんでした。
class ShopsController < ApplicationController
def new
@shop = Shop.new
@shop.shop_tastes.build
end
def create
@shop = Shop.new(shop_params)
@shop.save
end
private
def shop_params
params.require(:shop).permit( :shop_name, taste_ids:[])
end
end
以下正常に動いたコード
<%= form_with model: @shop, local: true do |f| %>
<%= f.label :shop_name %>
<%= f.text_field :shop_name, class: "form-control" %>
<%= f.collection_check_boxes :taste_ids, Taste.all, :id, :style, checked: @shop.style_ids ,include_hidden: false do |b| %>
<%= b.check_box %>
<%= b.text %>
<% end %>
<%= f.submit %>
<% end %>
正直fields_for
については、詳細な部分まで調べきれておらず細かい仕様が把握できていないので、今後時間のある時に調べます。