accepts_nested_attributes_forの使い方

参考

http://kzy52.com/entry/2013/07/10/200144

何がしたいか

form でネストする params を作って異なる model の属性を格納し、一回のアクションで同時にsaveしたい。

モデルで accepts_nested_attributes_for を宣言

ネストさせるパラメータの親のモデルクラスで accepts_nested_attributes_for を記述する。引数にはネストの子のモデルをシンボルで指定。

class Teacher < ApplicationRecord
  belongs_to :school
  accepts_nested_attributes_for :school
end

accepts_nested_attributes_for メソッドを使う前にモデル同士のリレーションが宣言されている必要がある。

こうすることで school_attributes メソッドが teacher モデルに追加される。

form を作る

新しいインスタンスを同時に作る場合。

  def new
    @teacher = Teacher.new
    @teacher.build_school
  end

これで @teacher と @teacher.school が同時に作られる。中身は空。

<%= render 'form', teacher: @teacher, school: @school %>

view でネストするパラメータを作るための form を作成

<%= form_with(model: teacher, local: true) do |form| %>
  <div class="field">
    <%= form.label :name, '名前' %>
    <%= form.text_field :name %>
  </div>
  <%= form.fields_for :school do |school_form| %>
    <div class="field">
      <%= school_form.label :name, '学校名' %>
      <%= school_form.text_field :name %>
    </div>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

form_with 内で 子params のための fields_for の領域を作る。

上の例でいうと form.fields_for とすることで params がネストされる。

ストロングパラメータ

# teachers_controller.rb 
def teacher_params
  params.require(:teacher).permit(:name, school_attributes: [:name])
end

ここで school_attributes をpermit に加えると、コントローラで入れ子のschoolの属性を @teacher.school として扱えるようになる。

save

def create
    @teacher = Teacher.new(teacher_params)
    respond_to do |format|
      if @teacher.save
        reset_session
        session[:teacher] = @teacher.id
        format.html { redirect_to @teacher, notice: '登録が完了しました.' }
        format.json { render :show, status: :created, location: @teacher }
      else
        format.html { render :new }
        format.json { render json: @teacher.errors, status: :unprocessable_entity }
      end
    end
  end

あとは @teacher のみを普通に save するときと同じ。

ポイント

accepts_nested_attributes_for はモデル同士のリレーションを前提としている。ただし、 accepts_nested_attributes_for はネストさせる params の親のモデルクラスで宣言する。今回のようにモデル自体のリレーションの親子関係 (belongs_to, has_many)と params の親子関係が逆だとややこしいので注意する必要がある。

まとめ

一つのフォームから複数のモデルを保存

トランザクション処理

上の二つでやろうとしていた処理が accepts_nested_attributes_for を使ったことで簡単に実装することができた!