0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

fields_forとaccepts_nested_attributes_forでネストされたフォームを簡単に扱う方法と注意点

Posted at

はじめに

日々勉強中の駆け出し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を使って、PostCommentの両方のデータを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_ifupdate_onlyなどのオプションをうまく活用することで、不要なレコードの作成や削除を防ぎ、フォームの挙動を制御できます。

最初は少し複雑に感じるかもしれませんが、使いこなすと非常に便利な機能です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?