RailsでForm Objectを導入はするのは今となっては珍しくもないかと思います。
それでも組織やチームに文化がない状態で投入してしまうと
それ自体が負債となる場合もあるんだろうなと感じたところもありました。
今回は普及のためにもForm Objectを作る方法を紹介してみようと思います
Form Objectとは?
単体のモデルに依存しない場合や
フォーム専用の特別な処理をモデルに書きたくない場合に用いたりします。
例えば1つのフォーム送信で複数のモデルの更新をしたい場合
バリデーションの責務が曖昧なものとなり関心事が多くなり
忘れたときに改めて解読するのも嫌になってしまうことがあります。
そのためにも責務を明確にするのは個人的には大切かなと。
責務ばかりを気にしていたら速度が失われてしまうため
状況を見つつ使い分けはしたいところ。。
メリットはあるか?
どんなに便利な手法だとしても
用法用量を守って触らないとろくな事にならないのはお察し。。
気を取り直してどういうメリットが有るかですが
- ActiveRecordと同じバリデーションを使うことができる
- Form Objectにパラメーターをそのまま渡すことができるためエラーメッセージを戻す処理が書きやすい
- 責務がモデルとは言い切れないビジネスロジックをcontroller/viewから切り離せる
- いわゆる単一責務の状態となりやすい
##ユースケース
- ユーザからの問い合わせを受け付けるフォームを実装したい
- 未登録ユーザは氏名とメールアドレス、内容の取得と通知を目的とする
最初はこれでよかったのかもしれない。
ただ実際にふたを開けてみたら
登録済みのユーザはIDと内容だけがいいとか
そもそも入力項目を増やしたいだとか経験がありますよね。
##Form
まずは今回の用件以外の関心ごとを減らすためにも
Form Objectの定義します
class ContactForm
include ActiveModel::Model
attr_accessor :first_name, :last_name, :email, :body
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true
validates :body, presence: true
def save
return false if invalid?
# 保存, 通知, ロギング等
true
end
end
Controller
次にControllerの処理です。
Form Objectにロジックを寄せたおかげで
scaffoldで作られようなシンプルな形を維持できます。
class ContactController < ApplicationController
def index
# お問い合わせ一覧
end
def new
@contact = ContactForm.new
end
def create
@contact = ContactForm.new(set_params)
if @contact.save
redirect_to :contacts, notice: 'お問い合わせを受け付けました。'
else
render :new
end
end
private
def set_params
params.require(:contact).permit(:first_name, :last_name, :email, :body)
end
end
Routes
Rails.application.routes.draw do
resources :contacts, only: [:index, :new, :create]
end
まとめ
Form Objectは一つの選択肢ですが
導入もしやすくわかりやすいのはいいところではないかと思います。
現実はこんなにシンプルには行きませんが手段として知っているだけでも選択肢が増えるかと思います。
複数人で開発を行う場合は
レイヤーの立場を明確にするなど
認識を合わせてからの導入が良いのだろうという反省のお話でした。