Formオブジェクト
今回のお題
今回のお題はRailsのFormオブジェクトです。
個人的に苦手なところなので、この機会にしっかりと覚えたいと思います。
目次
- Formオブジェクトとは
- Formオブジェクトの利点
- Formオブジェクトの作り方
- classの宣言
- include
- attr_accessorメソッド
概要〜Formオブジェクトとは
Formオブジェクトとは、複数のモデルを足し合わせたモデル(のようなもの)のことを言います。
一度のリクエストで複数のテーブルと同時にやり取りする場合に用いられます。
例えば、生徒がそれぞれどのサークルに所属しているのかを管理しているとします。
この時、テーブルは以下のようになります。
studentsテーブル
- id
- name
circlesテーブル
- id
- name
student_circlesテーブル
- id
- stundent_id
- circle_id
一人一人の生徒は複数のサークルに所属することもできるため、studentsテーブルとcirclesテーブルは多数対多数のアソシエーションになり、中間テーブルの出番になります。
(やや無理矢理ですが)studentの新規登録の際にサークルも選択する仕組みになっているとすると、studentsテーブルとstudent_circlesテーブルを同時に操作することになりますよね。
このような時には、両者を一まとめにしたFormオブジェクトを用いることが多いです。
Formオブジェクトの利点
Formオブジェクトが何なのかは分かったものの、一体何が便利なのでしょうか?
その答えは、「バリデーションなどの記述を一箇所にまとめられること」です。
例えば、上記の例で生徒の新規登録の際に表示されるバリデーションメッセージがおかしかったときに、どのファイルを見れば良いでしょうか?
答えは、「Student.rbとCircle.rbの両方」です。
バリデーションはModelに記述しますが、今回は2つのモデルを同時に操作しているので、どちらが原因の可能性もありますからね。
もちろん、"Name can't be blank"などのメッセージであればStudent.rbの方だな、などと検討をつけることもできますが、テーブル設計が複雑になるとそれも難しくなります。
では、もしFormオブジェクトを利用していればどうでしょうか?
例えば、StudentStudentCircleモデルを作成し、StudentモデルとStudentCircleモデルの両方に関するバリデーションをそこにまとめていたとします。
そうすると、何かしらバリデーションの記述を確認したい時には常にStudentStudentCircle.rbを見れば良いので、前者に比べて負担が軽くなりますよね。
これが、Formオブジェクトを利用するメリットです。
Formオブジェクトの作り方
Formオブジェクトの作り方は以下の通りです。
細かい解説は後に譲るとして、まずは全体像を。
ファイルの作成
StudentStudentCircle.rbを手動で作成。
ターミナルコマンドは使わずに行います。
Formオブジェクトはテーブルを持たないためです。
ファイルの編集
ファイルの中身は空のはずなので、以下の内容を追記します。
class StudentStudentCircle
include ActiveModel::Model
attr_accessor :name, :student_id, :circle_ids
validates :name, presence: true
....
end
以下、色分けしている部分の解説です。
クラスの宣言
class StudentStudentCircleの部分です。
ターミナルコマンドで生成したモデルであれば、
class Student < ApplicationRecord
end
という記述が最初から用意されていました。
しかし、Formオブジェクトのファイルは手動で作成しているため、クラスの定義自体を自分で行う必要があります。
include
次の話題は、2行目のinclude ActiveModel::Modelについてです。
ActiveModel::Modelというクラスをinclude、すなわち機能を取り込む処理になります。
この記述の役割は、以下の2点です。
上記の機能はどちらも、Railsのモデルにはデフォルトで備わっています。
これは、ターミナルコマンドで生成したモデルがApplicationRecordクラスを継承しているためです。
しかし、Formオブジェクトは他のクラスを継承しているわけではないので、必要な機能は全て自分で用意しないといけないというわけですね。
attr_accessorメソッド
次の話題は、attr_accessor :name, :student_id, :circle_idsの部分についてです。
この記述の役割は、ゲッターとセッターの定義ですね。
ゲッターとセッターはインスタンスのインスタンス変数の値を取得したり設定したりするためのものです。
例えば、student.nameでその生徒の名前が出力されたり、student.name = xxx とすることでその生徒の名前が上書きされたりするのは、Studentクラスに対してゲッターとセッターが定義されているからです。
このゲッターとセッターもApplicationRecordからの継承によって定義されるものなので、Formオブジェクトに関しては自分で定義する必要があります。
バリデーションの記述
validates :name, presence: trueの部分ですね。
基本的には何の変哲もないというか、元のクラスにあったバリデーションをそのまま写せば良いだけです。
注意点を挙げるとすれば、元々は外部キーだったカラムに対してもpresence: trueのバリデーションが必要になることぐらいでしょうか。
上記の例で言えば、student_idとcircle_idがそうですね。
外部キー由来のカラムについては、マイグレーションファイルで外部キー制約をかけていることによりpresence: trueが不要になっています。
ですが、現状はstudent_idやcircle_idをFormオブジェクトの変数として扱っている(=元のテーブルとは関係がないものとして扱っている)ので、それらに対してもバリデーションが必要になるというわけです。