34
24

More than 3 years have passed since last update.

accepts_nested_attributes_for メソッドを攻略する!

Last updated at Posted at 2020-12-05

最初は何が何だか分からなかった"accepts_nested_attributes_for"なるメソッドですが、使っているうちに便利さを実感するようになりました。

Active Recordはaccepts_nested_attributes_forメソッドの解説 (Railsガイド)
Active Recordはaccepts_nested_attributes_forメソッドの解説 (Ruby on Rails英語リファレンス)

要するに親モデルを保存するときに、Associationで関連づけた子モデルも一緒に保存することができるメソッドです。例えばuserが複数のブログを書いている場合。has_many と belongs_to をそれぞれ指定して、今回は user に accepts_nested_attributes_forメソッドを設定します。

modelの記述

models/user.rb
class User < ApplicationRecord
  has_many :blogs, dependent: :destroy
  accepts_nested_attributes_for :blogs, allow_destroy: true
end
models/blog.rb
class Blog < ApplicationRecord
  belongs_to :user
end

user.rb の方に記述している allow_destroy: true についてはこちらに記述がありますが、関連付けられたオブジェクトをユーザーが削除できるようにするために設定します。よく分からない、という場合はとりあえず accepts_nested_attributes_forメソッドの後ろにとりあえず記述しておく、と覚えておくでもいいかもしれません。

controllerの記述

user_controller.rb
#説明に必要なアクションのみ抜粋しています
class UserController < ApplicationController

  def new
    @user = User.new
    @user.blogs.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to users_path
    else
      render :new
    end
  end

  private
  def user_params
    params.require(:user).permit(:name, :address, :age, blogs_attributes: [:title, :content, :blog_num])
  end

end

accepts_nested_attributes_forメソッドに関連するのは2か所。

① new アクションの @user.blogs.new
② user_params の blogs_attributes: [:title, :content, :blog_num]

①はRailsガイドにも説明がありました。

(Railsガイドより)
フィールドのセットが少なくとも1つはユーザーに表示されるように、コントローラで1つ以上の空白の子を作成しておくというのはよく行われるパターンです。

@user = User.new だけだと user の入力欄しか表示されないので、controller で①の @user.blogs.new を記述して、子要素の入力フォームを表示させる準備をしています(この後viewファイルでフォームを表示させる記述をする必要があります)。

そして分かりにくかったのが②です。
Railsガイドによると

(Railsガイドより)
accepts_nested_attributes_forヘルパーが受け取るのはこのようなパラメータの名前です。たとえば、2つの住所を持つユーザーを1人作成する場合、送信されるパラメータは以下のようになります。
{
 'person' => {
  'name' => 'John Doe',
  'addresses_attributes' => {
   '0' => {
    'kind' => 'Home',
    'street' => '221b Baker Street'
   },
   '1' => {
    'kind' => 'Office',
    'street' => '31 Spooner Street'
   }
  }
 }
}
:addresses_attributesハッシュのキーはここでは重要ではありません。各アドレスのキーが重複していなければそれでよいのです。

つまり上のパラメーターの場合、 addresses_attributes のキーである kind と street が2回ずつ登場しますが、重複は気にせず1回ずつパラメーターに記述してあげればよいとのこと。

その結果strong parameterは下記のような形に。通常のstrong parameterの中に addresses_attributes を並べて、その中に[ ]でキーを並べています。

(Railsガイドより)
def create
@person = Person.new(person_params)
# ...
end

private
def person_params
params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street])
end

今回の私のケースで言うと、親モデルの name, address, age に加えて子要素の title, content, blog_num というキーをルールに従って記述すると、下記のような形に。

controllerの記述

users_controller.rb
  def user_params
    params.require(:user).permit(:name, :address, :age, blogs_attributes: [:id, :title, :content, :blog_num])
  end

ちなみに blogs_attributes の後の :id というのがかなり重要だったことに後から気付いたのですが・・・ここは後日別の記事に書こうと思います。

そして新規作成欄。

new.html.erb
<%= render "form" %>
_form.html.erb
<%= form_with(model: @user, local: true) do |form| %>
    <%= form.label :名前 %>
    <%= form.text_field :name %>

    <%= form.label :住所 %>
    <%= form.text_field :address %>

    <%= form.label :年齢 %>
    <%= form.text_field :age %>

  <%= form.fields_for :blogs do | blog | %>

     <%= blog.label :title, "タイトル" %>
     <%= blog.text_field :title %>

     <%= blog.label :content, "本文" %>
     <%= blog.text_field :content %>

     <%= blog.label :blog_num, "記事番号" %>
     <%= blog.text_field :blog_num %>

  <% end %>
  <div><%= form.submit :登録する%></div>
<% end %>

新規作成画面でのポイントは ③fields_for になります。上のコードで言いますと親モデルの内容を form で受けて、その後に続けて fields_for を記述します。形としては上のコードにあるように

<%= form.fields_for :blogs do | blog | %>

となります。blogs の内容を blog という形で1つずつ取り出して、 label で入力欄の説明、 text_field で入力欄を表示させる形。この辺は accepts_nested_attributes_for を使わない場合でも同じですね。

まとめ

accepts_nested_attributes_for メソッドは最初苦手だったのですが、Railsガイドが結構分かりやすく説明してくれていたので助かりました。今回の記事を書くにあたってもRailsガイドをかなり参照しています。

個人的にポイントは下記の3点でした(今回のモデルの場合)。
①controller に @user.blogs.new を設定する
②user_params の blogs_attributes の記述
③view の新規作成画面で fields_for の使い方

ちょっとややこしいですが、このあたりは「習うより慣れろ」で、動きを理解した上でたくさん使ってみて覚えてしまうのが正解の気がしています。

34
24
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
34
24