経緯

いろいろなシステムを構築していると、グループを作成するときにメンバーも同時に作成したくなることがあります。

例えば、以下のようなモデルがあるとして、GroupnewのときにMemberも作成することを考えてみます。

class Group < ActiveRecord::Base
  has_many :members
end

class Member < ActiveRecord::Base
  belongs_to :group
end

簡単なやり方

gemのインストール

まず、簡単にフォームを扱うために、nested_formというgemを入れます。
特に、追加できるMemberの数を可変にすることがなければ、このgemは必要ありません。

Gemfile

gem 'nested_form'
$ bundle install

nested_formはViewで使える便利なHelperを用意してくれます。

Modelの修正

まずはModelを編集します。親のGroupに対して、

accepts_nested_attributes_for :members, allow_destroy: true

を追記してやりましょう。子モデルのフィールドを扱ってもいいよー、という設定です。
allow_destroyについては後ほど書きます。

Controllerの修正

app/controllers/groups_controller.rb

def new
  @group = Group.new
  @group.members.build # 追記
end

Groupを作成するとき、空のGroupを予め作っておくのと同様に、空のMemberを作成しておいてやります。

app/controllers/groups_controller.rb

def group_params
  params.require(:group).permit(
    :name,
    members_attributes: [:name] # 追記
  )
end

members_attributesGroupに紐づくMemberのフィールドになります。
このmembers_attributesGroup自体にぶっこんでやれば、自動的にMemberが作成されるようになります。

Viewの修正

app/controllers/views/groups/new.html.erb

<%= nested_form_for @group do |f| %>

    <%= f.label :name %>
    <%= f.text_field :name %>
  
  <%= f.fields_for :members do |mf| %>
    <%= mf.label :name %>
    <%= mf.text_field :name %>
    <%= mf.link_to_remove '駆逐してやる!' %>
  <% end %>
  <%= f.link_to_add '前方に20m級!' :members %>
  <%= f.submit %>
<% end %>

ひとまず、実行してみると、「前方に20m級!」を押すと、20m級の巨人かどうかは分かりませんが、memberのフィールドが追加され、「駆逐してやる!」を押すと、そのフィールドが削除されることがわかると思います。1
こいつが、nested_formの便利機能です。

簡単な解説

Viewで、form_forの代わりにnested_form_forを使うことで、link_to_addlink_to_removeのようにnested_formの便利関数を使えるようになりました。
名前からお分かりでしょうが、link_to_add '前方に20m級!', :membersMemberのフィールドを追加する「前方に20m級!」というボタンを作成し、
link_to_remove '駆逐してやる!'Memberを削除する「駆逐してやる!」というボタンを作ってくれます。
ちなみに、link_to_removeは先ほど、Modelのところで、少し触れたallow_destroy: trueがないと動きません。ご注意ください。
nested_formを使わないときはこれらの機能が使えないので、上記サンプルからは削除しましょう。

f.fields_for :membersはこのBlock内は@groupに紐付いたmemberのフィールドですよ!といっています。
出力されるHTMLを確認してみればわかりますが、この中のフィールドのnameにControllerで定義したmembers_attributesが含まれています。
mfという変数を使うこと以外は通常のform_forの中と同じように記述できます。

最初に2つ以上のフィールドを作っておきたい。

最初に作っておきたいフィールドの数を変更したいときはController内に記述した

@group.members.build

の記述を

2.times { @group.members.build }

のようにしてやればOKです。

逆に最初からMemberのフィールドを用意しない場合はこの記述自体を削除しましょう。

まとめ

以上のように意外と簡単に作成できるのでいろいろと活用していけると思います。
まとめると、

  • 親Modelに子Modelのフィールドへのアクセス許可を与える
  • Controllerで予め空の子Modelを作成してやる
  • Controllerで**_attributesというparamsをModelに渡すように実装する
  • Viewでfields_forを使って子モデルのフィールドを作る

となります。

nested_forを使うと他にもいくつかカスタマイズも可能なので、それはまたの機会に。


  1. 進撃の巨人を見てから、大学時代の同級生のデカイやつの後頭部をドーンとやりたい衝動に駆られました。