例えば以下のように、一つの投稿(post)の中にさらに複数の投稿(item)を追加できるようにしたい場合、
そんな時は、idなどのパラメータの受け渡しの都合上、ルーティングを以下のようにネスト(nest)して親子関係にした方が実装しやすいかと思います。
resources :posts do
resources :items
end
Prefix Verb URI Pattern Controller#Action
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
new_post GET /posts/new(.:format) posts#new
edit_post GET /posts/:id/edit(.:format) posts#edit
post GET /posts/:id(.:format) posts#show
PATCH /posts/:id(.:format) posts#update
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
post_items GET /posts/:post_id/items(.:format) items#index
POST /posts/:post_id/items(.:format) items#create
new_post_item GET /posts/:post_id/items/new(.:format) items#new
edit_post_item GET /posts/:post_id/items/:id/edit(.:format) items#edit
post_item GET /posts/:post_id/items/:id(.:format) items#show
PATCH /posts/:post_id/items/:id(.:format) items#update
PUT /posts/:post_id/items/:id(.:format) items#update
DELETE /posts/:post_id/items/:id(.:format) items#destroy
そこで、ルーティングが親子関係(親モデル:post、子モデル:item)にある場合において、子モデルに対するform_withの書き方について書き残しておこうと思います。
前提条件
- 2つのモデルが存在する(postモデルとitemモデル)
- postモデルはitemモデルに対して「1対多」の関係にある
Rails.application.routes.draw do
・・・
resources :posts do
resources :items
end
end
# app/models/post.rb
class Post < ApplicationRecord
has_many :items, dependent: :destroy
end
# app/models/item.rb
class Item < ApplicationRecord
belongs_to :post
end
ネストした子モデルに対するform_withの書き方
ネストした子モデル(item)でform_withを書く場合、通常と書き方が少し異なります。
結論から言うと、以下のようにitemモデルのフォーム(form_with)に複数のインスタンスを渡せばOKです。
# モデルのインスタンスを複数(@post, @item)渡す
<%= form_with(model: [@post, @item], local: true) do |f| %>
・・・
<% end %>
ネストした子モデル(item)に通常通りform_withを書くと起こるエラー
今回の場合、ネストした子モデル(item)の入力フォーム画面を作成する時、
<%= form_with(model: @item, local: true) do |f| %>
・・・
<% end %>
itemモデルだからmodel: @item
だな、といつも通りにform_withを書てしまいがちですが、そうすると以下のようなエラーが起こります。
NoMethodError in …
undefined method `items_path` for …
どうやら、items_path
が定義されていませんよ、と言うことらしい。
通常、ルーティングの設定で以下のようにitemモデル(itemsコントローラー)に対してresoucesを用いた場合、
resources :items
itemモデルのindexへのパスはitems_path
となりますが、今回の場合はitemモデルのresoucesはネストされているため、itemモデルへのindexへのパスが以下のようにpost_items_path
と変更されます。
# 通常の場合
items_path GET /items(.:format) items#index
# itemが子モデルの場合(親モデルはpost)
post_items_path GET /posts/:post_id/items(.:format) items#index
そのため、form_withのモデルを@itemとした場合、
<%= form_with(model: @item, local: true) do |f| %>
・・・
<% end %>
入力内容を送信する際は、自動的にitems_path
に対してPOSTメソッドで送信しようとします。
しかし、今回はネストしたことによりitemモデルのindexへのパスはすでにitems_path
からpost_items_path
へと変更されているため、form_withを上記のように書くと「items_pathが見つかりませんよ!」と怒られてしまうのです。
解決策
以下のように、子モデル(item)のform_withに、親子関係にあたるモデルのインスタンスを全て入れることで解決します。
# モデルのインスタンスを複数(@post, @item)渡す
<%= form_with(model: [@post, @item], local: true) do |f| %>
・・・
<% end %>
上記のように書くことで、送信先のurlであるpost_items_path
を自動的に生成してくれます。
また、親モデル(post)から見た子モデル(item)への入力フォームへのパスは以下のように指定します。
・・・
<div class="mb-3">
>> <%= link_to "アイテムを追加する", new_post_item_path(@post) %>
</div>
以上です。
コメント