最初は何が何だか分からなかった"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の記述
class User < ApplicationRecord
has_many :blogs, dependent: :destroy
accepts_nested_attributes_for :blogs, allow_destroy: true
end
class Blog < ApplicationRecord
belongs_to :user
end
user.rb の方に記述している allow_destroy: true についてはこちらに記述がありますが、関連付けられたオブジェクトをユーザーが削除できるようにするために設定します。よく分からない、という場合はとりあえず accepts_nested_attributes_forメソッドの後ろにとりあえず記述しておく、と覚えておくでもいいかもしれません。
####controllerの記述
#説明に必要なアクションのみ抜粋しています
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の記述
def user_params
params.require(:user).permit(:name, :address, :age, blogs_attributes: [:id, :title, :content, :blog_num])
end
ちなみに blogs_attributes の後の :id というのがかなり重要だったことに後から気付いたのですが・・・ここは後日別の記事に書こうと思います。
そして新規作成欄。
<%= render "form" %>
<%= 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 の使い方
ちょっとややこしいですが、このあたりは「習うより慣れろ」で、動きを理解した上でたくさん使ってみて覚えてしまうのが正解の気がしています。