【週末Rails勉強会】1つのformで親子孫関係のテーブルデータを登録する方法

ruby開発
スポンサーリンク
railsで一つのフォームで複数テーブルにデータを登録したかった。
とりあえずform_tagでパラメータを全て送ってコントローラー側で1つずつ登録するという処理を書いてみたが、拡張性、メンテナンス性が低そうだし、なによりrailsの良さを活かしきれてない感があったのでform_forで一括で登録するようにした。
これに結構ハマったので書き留めておきます。
railsのこの辺りの仕様って便利ですが、理解するのに時間かかりますね・・・。
今はform_withが推奨されているようですが、一旦それは考えないで起きます(現実逃避)
スポンサーリンク

やりたい事

アソシエーションで紐づいている親子孫関係のレコードデータを1つのformで一括登録する。今回は、Company(会社), Department(部署), Member(人員)の関係で試してみる。

環境

  • MacOS Sierra
  • Rails5.2.2.1
  • Mysql5.7.18

手順

  1. モデルの作成、編集
  2. コントローラーの作成
  3. フォームの作成

モデルの作成、編集

以下のように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

フォームの作成

<%= 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が推奨されているみたいです

コメント