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が推奨されているみたいです
コメント