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

何がしたいのか?

belongs_to/has_manyの関係を持つ2つのモデルのインスタンスをbelongs_to側のformから同時に作成したい。

view

form.fields_for のようにして params を入れ子にすることは避けた。理由はリレーションの親子関係が逆になってややこしくなると思ったため。今回は fields_for @school にして別のパラメータとしてコントローラに送るようにした。

<%= form_with(model: teacher, local: true) do |form| %>

    中略

  <div class="field">
    <%= form.label :name, '名前' %>
    <%= form.text_field :name %>
  </div>

    中略

  <%= 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 %>

コントローラ

belongs_to のリレーションがあると自動的にその属性で reqire :true になる。また、build_〇〇 というメソッドが使えるようになる。

@school = @teacher.build_school(school_params)
@school.save

とすることで @school の生成と @teacher の school_id の取得を同時に行うことができた。

class TeachersController < ApplicationController
  def new
    @teacher = Teacher.new
    @school  = School.new
  end
    
  def create
    @teacher = Teacher.new(teacher_params)
    create_school
    if @teacher.save
      reset_session
      session[:teacher] = @teacher.id
      redirect_to root_path
    else
      redirect_to root_path
    end
  end

  private
    def create_school
      @school = @teacher.build_school(school_params)
      unless @school.save
        redirect_to root_path
      end
    end

    def teacher_params
      params.require(:teacher).permit(:school_id, :name, :password, :password_confirmation, :email)
    end

    def school_params
      params.require(:school).permit(:name)
    end
end

感想

コントローラに別のモデルを保存する処理を書いて良いものか少し迷った。TeachersControllerにSchoolsControllerを継承するという手もあるけどそれはそれで微妙な気がしたのでやめた。

追記

今回のパターンだと accepts_nested_attributes_for を使った方が良さそうです。下記のページでまとめました。

accepts_nested_attributes_forの使い方