はじめに
日々勉強中の駆け出しWebエンジニアです。最近のプロジェクトで多用しているfields_for
について学んだことをまとめようと思います。
Railsのフォームを作成する際に、ネストしたモデルを扱う場面で登場するfields_for
の使い方で、フォームが表示されなくなったり、ストロングパラメータの設定で悩むことがあったので、これから学ぶ方の助けになれば幸いです。
fields_forとは
fields_for
は、Railsのフォームビルダーの一部であり、親モデルにネストされた子モデルに対するフォーム要素を作成するために使用されます。form_with
などとは異なり、fields_forは親モデル内で関連する子モデルに対してのフォームを生成できます。
例えば、ある親モデル(Post)が子モデル(Comment)を持つ場合、投稿フォーム内にコメントフォームを作成することができます。これにより、1つのフォームでPostとCommentの両モデルを更新することができます。
基本的な使い方としては、form_withのブロック内でfields_forを使用します。
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :comments do |c_form| %>
<%= c_form.text_field :content %>
<% end %>
<%= f.submit %>
<% end %>
この例では、@post
という親モデルのフォームの中で、comments
という子モデルのフォームも含まれています。
実装手順
filds_forを使って親モデルと子モデルを更新するフォームの実装手順について説明していきます
Nested attributesを使ってネストされたモデルを設定
fields_forを使うためには、親モデルと子モデルが正しく関連付けられている必要があります。
また、親モデルにはaccepts_nested_attributes_for
を記述する必要があります。
# 親モデル(Post)
class Post < ApplicationRecord
has_many :comments
accepts_nested_attributes_for :comments
end
# 子モデル (Comment)
class Comment < ApplicationRecord
belongs_to :post
end
ここでaccepts_nested_attributes_for :comments
があることで、PostモデルのフォームからCommentモデルの属性を受け取って保存することが可能になります。
accepts_nested_attributes_for のオプション
accepts_nested_attributes_for
には、フォームの挙動を柔軟に行える便利なオプションがあります。
1. allow_destroy
allow_destroyをtrueに設定すると、子モデルのレコードをフォームから削除できるようになります
accepts_nested_attributes_for :comments, allow_destroy: true
2. reject_if
このオプションを使うと、特定の条件(コメントの空の場合など)に合致する子モデルのレコードを無視することができます。これにより空のコメントフィールドがあっても、無意味なレコードが作成されることを防ぎます
accepts_nested_attributes_for :comments, reject_if: proc { |attributes| attributes['content'].blank? and attributes['title'].blank? }
3. update_only
通常、Railsは新規レコードの作成か既存レコードの更新かを自動的に判断しますが、このオプションをtrueに設定すると、既存レコードを必ず更新し、新しいレコードは作成しなくなります。
accepts_nested_attributes_for :comments, update_only: true
viewにフォームを実装
次に、フォームのviewを実装します。
ここでfields_for
を使って、Post
とComment
の両方のデータを1つのフォームで送信できるようにします。
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :comments do |comment_form| %>
<%= comment_form.text_field :content %>
<% end %>
<%= f.submit "Save Post" %>
<% end %>
このフォームでは、@post
という親モデルのタイトルフィールドと、ネストされたcomments
のフィールドが一緒に表示されます。
controllerでストロングパラメータを設定
ネストされたモデルの属性を受け取るには、ストロングパラメータでnested_attributes
を許可する必要があります。これを忘れると、フォームから送信されたデータが適切に処理されなくなります。
def post_params
params.require(:post).permit(:title, comments_attributes: [:content])
end
注意点
fields_for
を使用するにあたり引っかかったポイントがあるので紹介します。
子モデルが存在しない場合にフォームが表示されない
fields_for
を使う際に注意すべきポイントの一つとして、子モデル(Commentなど)が存在しない場合、フォームが表示されないという問題があります。
これを回避するには、親モデルに空の子モデルをあらかじめ用意しておく必要があります。
class PostsController < ApplicationController
def new
@post = Post.new
@post.comments.build # 空のCommentオブジェクトを作成
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, comments_attributes: [:content])
end
end
このようにして、子モデルがなくてもフォームに必ず1つのコメントフィールドが表示されるようになります。
最後に
fields_for
を使うことで、親モデルと子モデルを一括で管理するフォームを作成することができます。
そしてreject_if
、update_only
などのオプションをうまく活用することで、不要なレコードの作成や削除を防ぎ、フォームの挙動を制御できます。
最初は少し複雑に感じるかもしれませんが、使いこなすと非常に便利な機能です。