はじめに
「ユーザー情報」「本」「ジャンル」、の3つの情報を1つのフォームから送信して、
別々のテーブルに保存する機能をFormオブジェクトを使って実装する。
具体的に言うと、自分で定義したクラスに、form_withメソッドに対応する機能とバリデーションを行う機能を持たせ、そのクラスのインスタンスメソッドで複数のテーブルへの保存の処理などを行う。
Formオブジェクト
Formオブジェクトとは、Railsのデザインパターンの1つです。
このデザインパターンは、「1つのフォーム送信で複数のモデルを操作したい場合」や、
「テーブルに保存しない情報に対するバリデーションを行いたい場合」に使います。
元々modelにバリデーション等様々な処理が書かれているものを、form層に切り出すことで多くのmodelで同一の処理を使えるということです。
#実装
form層に切り出すモデルファイル
にinclude 「ActiveModel::Model」 と 「attr_accessor」 を設定する
class UserBook
include ActiveModel::Model
attr_accessor :nickname, :age, :gender, :book_title, :genre
end
○ActiveModel::Model
あるクラスにActiveModel::Modelをincludeすると、そのクラスのインスタンスはActiveRecordを継承したクラスのインスタンスと同様に form_with や render などのヘルパーメソッドの引数として扱えたり、バリデーションの機能が使えるようになります。
Formオブジェクトパターンを実装するためにActiveModel::Modelをincludeしたクラスのことを「Formオブジェクト」と呼ぶこともあります。
○attr_accessor
記述したクラスに、ゲッターとセッターを定義してくれるメソッドです。与えられた引数をもとに属性を設定し、これを取得するメソッド(ゲッター)と更新するメソッド(セッター)を定義してくれます。
・値の取得ができるメソッドの定義(ゲッター)
・値の更新ができるメソッドの定義(セッター)
今回のフォームで利用する全ての属性(つまり、保存したい各テーブルのカラム名全て)についてゲッターとセッターを定義することで、このFormオブジェクトのインスタンスを生成した際にform_withの引数として利用できるようになります。
○切り出す処理を記述
class UserBook
include ActiveModel::Model
attr_accessor :nickname, :age, :gender, :book_title, :genre
with_options presence: true do
validates :nickname
validates :age
validates :genre
end
def save
user = User.create(nickname: nickname, age: age, gender: gender)
Book.create(book_title: book_title, user_id: user.id)
Genre.create(genre: genre, user_id: user.id)
end
end
設定したいバリデーション全てと、
def save ~ end 内では
フォームからパラメーターとして送られてきた情報をテーブルに保存する処理を書いています。
※Book・Genreオブジェクトでどのユーザーと結びついているのかを保存したいので、
ユーザー情報をuserに代入して、user_idカラムにuser.idで取得した1人のユーザー情報を保存させています。
○Formオブジェクトのインスタンスを生成
フォームから送られてきた全ての情報をハッシュ形式に整形するbook_paramsを定義し、
createアクションでこれを呼び出してUserBookクラスのインスタンスを作成することに利用しています。
class BooksController < ApplicationController
def index
end
def new
@user_book = UserBook.new
end
def create
@user_book = UserBook.new(book_params)
if @user_book.valid?
@user_book.save
redirect_to action: :index
else
render action: :new
end
end
private
def book_params
params.require(:user_book).permit(:nickname, :age, :gender, :book_title, :genre)
end
end
○Formオブジェクトのインスタンスを引数として渡す
new.html.erbのform_withメソッドに、作成したFormオブジェクトのインスタンスを引数として渡し、入力した情報がMVCを通って処理されるようにします。
<%= form_with(model: @user_book, url: donations_path, local: true) do |form| %>
<%= render 'error_messages', model: @user_book %>
# フォーム内容
<% end %>
この記事は下記を参考させていただいております。
https://product-development.io/posts/rails-design-pattern-form-objects