Posted at

【Rails】FormObjectを使ってほしい

RailsでForm Objectを導入はするのは今となっては珍しくもないかと思います。

それでも組織やチームに文化がない状態で投入してしまうと

それ自体が負債となる場合もあるんだろうなと感じたところもありました。

今回は普及のためにもForm Objectを作る方法を紹介してみようと思います


Form Objectとは?

単体のモデルに依存しない場合や

フォーム専用の特別な処理をモデルに書きたくない場合に用いたりします。

例えば1つのフォーム送信で複数のモデルの更新をしたい場合

バリデーションの責務が曖昧なものとなり関心事が多くなり

忘れたときに改めて解読するのも嫌になってしまうことがあります。

そのためにも責務を明確にするのは個人的には大切かなと。

責務ばかりを気にしていたら速度が失われてしまうため

状況を見つつ使い分けはしたいところ。。


メリットはあるか?

どんなに便利な手法だとしても

用法用量を守って触らないとろくな事にならないのはお察し。。

気を取り直してどういうメリットが有るかですが


  • ActiveRecordと同じバリデーションを使うことができる

  • Form Objectにパラメーターをそのまま渡すことができるためエラーメッセージを戻す処理が書きやすい

  • 責務がモデルとは言い切れないビジネスロジックをcontroller/viewから切り離せる


    • いわゆる単一責務の状態となりやすい




ユースケース


  • ユーザからの問い合わせを受け付けるフォームを実装したい

  • 未登録ユーザは氏名とメールアドレス、内容の取得と通知を目的とする

最初はこれでよかったのかもしれない。

ただ実際にふたを開けてみたら

登録済みのユーザはIDと内容だけがいいとか

そもそも入力項目を増やしたいだとか経験がありますよね。


Form

まずは今回の用件以外の関心ごとを減らすためにも

Form Objectの定義します


app/forms/contact_form.rb


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で作られようなシンプルな形を維持できます。


app/controllers/contact_controller.rb

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


config/routes.rb

Rails.application.routes.draw do

resources :contacts, only: [:index, :new, :create]
end


まとめ

Form Objectは一つの選択肢ですが

導入もしやすくわかりやすいのはいいところではないかと思います。

現実はこんなにシンプルには行きませんが手段として知っているだけでも選択肢が増えるかと思います。

複数人で開発を行う場合は

レイヤーの立場を明確にするなど

認識を合わせてからの導入が良いのだろうという反省のお話でした。