今回は一つのリクエストで、2つのモデルに対して保存処理を行う方法についてまとめます。例えば購入記録を保持するテーブルと、それに紐づく住所を保持するテーブルがあるとします。この時、どうすれば一回のリクエスで二つのテーブルに対しえて保存処理を行えるのでしょうか。その解決方法としてFormフォームオブジェクトパターンを新しく学んだので、備忘録としてまとめます。
Formフォームオブジェクトパターン
Formフォームオブジェクトパターンとは、一つのフォーム送信で複数のモデルを操作したい時に使います。一つのフォーム送信で複数のモデルを操作する時には例えば以下のような問題が発生します。
①一度で複数のモデルのバリデーションを通過させないといけない
②バリデーションで弾かれた場合、複数のモデルのエラーメッセージを表示させないといけない。
これらを実装しようとすると複雑なコードを書かなくてはなりません。
そういった問題を解決するためにFormフォームオブジェクトパターンを使います。
使い方としては
①modelsディレクトリ直下に自作ファイルを作成し、複数のモデルに関する処理をまとめて記述する
②コントローラーに自作したクラスのインスタンス変数を渡す
順番に解説していきます。
『今回はどこかにお金を送金するような仕組みを考えます。
モデルとしては次の二つです。
①moneysテーブル(:money、:user_id)
②addressテーブル(:city, :house_number,:money_id)
この2つのモデルを1回のリクエストで保存するという処理です』
新たにmodelsディレクトリ直下にファイルを作成し、クラスを定義する
まずはmdoels直下に自作ファイルを作ります。moneysテーブルとaddressテーブルをまとめて、「money_address.rb」とします。
そして次に、この自作したクラスの中に、2つのテーブルのカラムをまとめて記述します。
class OrderShipping
include ActiveModel::Model
attr_accessor :user_id, :money, :city, :house_number
end
ここでの注意点は、以下の二つです
①attr_accessor→ゲーターとセッターを定義してくれるメソッド。
②include ActiveModel::Model→ActiveModel::Modelをincludeすることで、form-withの引数にできたり、バリデーションの設定ができたりします。
自作クラスにバリデーションを設定する
次に自作したmoney_address.rbにmoneyテーブルとaddressテーブルのバリデーションを全て設定します。
class MoneyAddress
include ActiveModel::Model
attr_accessor :user_id, :money, :city, :house_number
with_options presence: true do
validates :user_id
validates :money
validates :city
validates :house_number
end
自作したクラスにバリデーションが設定できるのは、ActiveModel::Modelをincludeしているからです。
自作クラスに保存する処理を設定する
次に自作したorder_shippng.rbにmoneyテーブルとaddressテーブル、両方を保存する処理を加えます。
class MoneyAddress
include ActiveModel::Model
attr_accessor :user_id, :money, :city, :house_number
with_options presence: true do
validates :user_id
validates :money
validates :city
validates :house_number
def save
money = Money.create(user_id: user_id, money: money)
Address.create(city: city, house_number: house_number, money_id: money_id)
end
end
以上で自作クラスの設定は完了です。
コントローラーで自作クラスを呼び出す
コントローラーで、以下のように自作クラスを呼び出します。
class MoneyController < ApplicationController
def index
end
def new
@money_address = MoneyAddress.new
end
def create
@money_address = MoneyAddress.new(money_params)
if @money_address.valid?
@money_address.save
redirect_to root_path
else
render :new
end
end
private
def money_params
params.require(:money_address).permit(city: city, house_number: house_number, money: money).merge(user_id: current_user.id)
end
end
@money_address.valid?で自作クラスで定義したバリデーションを全て実行し、
@money_address.saveで自作クラスで定義した二つのモデルを保存しています。
Formフォームオブジェクトパターンの実装が完です。
これによりviewのform-withに@money_addressを渡すことができるので、
①一度で複数のモデルのバリデーションを通過させないといけない
②バリデーションで弾かれた場合、複数のモデルのエラーメッセージを表示させないといけない。
という問題を解決できます。
まとめ
今回は一つのフォーム送信で複数のモデルを操作する時に使う、Formフォームオブジェクトパターンについてまとめました。初学者にとっては迷いやすい部分だと思うので、今後も学んでいきたいと思います。