Railsで1つのフォームで複数のモデルを登録・更新させる

アソシエーションとかで紐付いている複数モデルを、1つのフォームで入力させて登録・更新する方法について調べた。

Model

companyモデルに会社の経理情報を持ったcompany_financialモデルが1対1でリンクしている。
ポイントとしては、company更新時に付随するcompany_financialを、accepts_nested_attributes_forに指定するところ。

company.rb
class Company < ActiveRecord::Base
  has_one :company_financial
  accepts_nested_attributes_for :company_financial
end
company_financial.rb
class CompanyFinancial < ActiveRecord::Base
  belongs_to :company
end

Controller

ポイントは以下の二つ。
- strong parameterに、company_financial_attributesを追加してあげる。(更新時のためにidもパラメータに含めておく)
- 付随するアソシエーションも同時に登録する場合はbuildであらかじめ作成しておくこと。(has_oneではbuildが使えないのでbuild_~~を使用します)

company_financial.rb
class CompaniesController < ApplicationController
  before_action :find_company, only: [:show, :edit, :update, :destroy]

  def new
    @company = Company.new
    @company.build_company_financial
  end

  def edit
  end

  def create
    @company = Company.new(company_params)
    @company.save!
    redirect_to company_path(@company)
  rescue
    render action: 'new'
  end

  def update
    @company.update!(company_params)
    redirect_to company_path(@company)
  rescue
    render action: 'edit'
  end

  private

    def find_company
      @company = Company.find(params[:id])
    end

    def company_params
      params.require(:company).permit(:name, company_financial_attributes: [:id, :bank_info])
    end

end

View

fields_forでネストすることで、アソシエーションのフィールドを設置できる。

companies/new.html.slim
= form_for @company, url: companies_path do |f|
  .form-group
    = f.label :name
    = f.text_field :name
  = form.fields_for :company_financial: do |fin|
    .form-group
      = fin.label :bank_info
      = fin.text_field :bank_info

Model間の関係はないが、1つのフォームで複数モデルの登録をしたい場合

strong parameterを分けて、それぞれでsaveすればOK.

company_financial.rb
class CompaniesController < ApplicationController
  before_action :find_company, only: [:show, :edit, :update, :destroy]

  def new
    @company = Company.new
    @user = User.new
  end

  def create
    @company = Company.new(company_params)
    @company.save!
    @user = User.new(user_params)
    @user.save!
    redirect_to company_path(@company)
  rescue
    render action: 'new'
  end

  private

    def company_params
      params.require(:company).permit(:name)
    end

    def user_params
      params.require(:user).permit(:name)
    end
end

View

companies/new.html.slim
= form_for @company, url: companies_path do |f|
  .form-group
    = f.label :name
    = f.text_field :name
  = form.fields_for @user do |user|
    .form-group
      = user.label :name
      = user.text_field :name