railsで一つのフォームで複数テーブルにデータを登録したかった。
とりあえずform_tagでパラメータを全て送ってコントローラー側で1つずつ登録するという処理を書いてみたが、拡張性、メンテナンス性が低そうだし、なによりrailsの良さを活かしきれてない感があったのでform_forで一括で登録するようにした。
これに結構ハマったので書き留めておきます。
railsのこの辺りの仕様って便利ですが、理解するのに時間かかりますね・・・。
今はform_withが推奨されているようですが、一旦それは考えないで起きます(現実逃避)
やりたい事
アソシエーションで紐づいている親子孫関係のレコードデータを1つのformで一括登録する。今回は、Company(会社), Department(部署), Member(人員)の関係で試してみる。
環境
- MacOS Sierra
- Rails5.2.2.1
- Mysql5.7.18
手順
- モデルの作成、編集
- コントローラーの作成
- フォームの作成
モデルの作成、編集
以下のように3つのテーブルを作成
conpanies(親)
– id
– conpany_name
departments(子)
– id
– company_id
– department_name
members(孫)
– id
– department_id
– member_name
テーブル名:複数形
外部キー:該当テーブルの単数形_id
rails g model テーブル定義 で作成すると良い。細かい部分は手動でmigrationファイルを修正しても良いしよしなに作成してください。
modelファイルにアソシエーション関係を記入する。本家のドキュメントを参照すると良いです。
今回はテストのため、company:department:member を 1:1:n として定義したいと思います。各モデルのアソシエーションの設定は以下のようになる。
class Company < ApplicationRecord has_one :department, inverse_of: :company accepts_nested_attributes_for :department end
class Department < ApplicationRecord belongs_to :company, inverse_of: :department has_many :members, inverse_of: :department accepts_nested_attributes_for :members end
class Member < ApplicationRecord belongs_to :department, inverse_of: :members end
ポイントは下記になります。
- has_one: 1:1の関係を表す
- has_many: 1:nの関係を表す
- belongs_to: 子が紐づいている親を表す
- accepts_nested_attributes_for: 親作成時に子供も作成できるようになる
- inverse_of: 同時作成時に外部キーがない事によるid違反を解消してくれる。
コントローラーの作成、編集
class CompanyController < ApplicationController
def new
# インスタンス作成
@company = Company.new
department = @company.build_department
department.members.build
end
def create
# 親子孫のデータの作成
@company = Company.create(company_params)
end
private
def company_params
# formから送られてくるパラメータの取得(ストロングパラメーター)
params.require(:company).permit(:company_name, department_attributes:
[
:department_name, members_attributes:
[
:member_name
]
]
)
endアソシエーションによって追加されるメソッドとしてbuildがある。(newと同じらしい)
これはhas_oneの場合と、has_manyの場合で記述方法が異なるので気をつける。
has_one: build_model
has_many: model.build
has_many: model.build
フォームの作成
<%= form_for @company, url: company_path do |f| %>
<%= f.label :company_name, "会社名" %>
<%= f.text_field :company_name, class: 'form_control' %>
<%= f.fields_for :department do |d| %>
<%= d.label :department_name, "部署名" %>
<%= d.text_field :department_name, class: 'form_control' %>
<%= d.fields_for :members do |m| %>
<%= m.label :member_name, "人員名" %>
<%= m.text_field :member_name, class: 'form_control' %>
<% end %>
<% end %>
<%= f.submit "送信" %>
<% end %>このようにフォームを作成できる。
fields_forを使用して、子供のフォーム項目を作成していくのがキモ
これで、会社名、部署名、人員名を入力してpostすると、3テーブルにそれぞれデータが作成されます。外部キーのidもバッチリ入ってます。
routesの設定は忘れないようにしてください。
※rails5だとform_tag, form_forは非推奨で form_withが推奨されているみたいです


コメント