Ruby
Rails
Validation

フォームオブジェクトでバリデーション 初心者→中級者へのSTEP2/25

フォームオブジェクトでバリデーション 

はじめに
前回の記事ではController, Model, View, Databaseでのバリデーション(データの検証)の役割を説明しました。
今回はControllerにおいて、その役割をどう果たすかの具体例です。
Controllerでの入力値を検証するフォームオブジェクトについて説明してみます。

フォームオブジェクト

先に言っとくと、これはRailsの仕様や機能ではなく、デザインパターンの一つです。(一応Railsも推奨しているパターンです。)

フォームオブジェクトのメリットですが、

  • DBに関連しないフォームでもActiveRecordと同じバリデーションが使える。
  • Model,Controllerに分散されがちなロジックをform object内に集められる。

個人的には前回の記事で書いたバリデーションの役割分担をできて、コードもスッキリするって感じかな...

早速書いてきます。

コード

Userコントローラー

users_controller.rb
class UsersController < ApplicationController
  def new
    @user = FormUserNew.new
  end

  def create
    @user = FormUserNew.new(user_params)
    if @user.save
      redirect_to '/'
    else
      render :new
    end
  end


  private
    def user_params
      params.require(:user).permit(:name, :email)
    end
end

いつもならここはUser.newですがUserモデルのオブジェクトではなく、新しく作ったフォームオブジェクトのFormUserNewを用います。

Userモデル

user.rb
class User < ApplicationRecord
#今回入力値の検証ということでmodelには何も書きません。
end

User#newのview

new.haml
= form_with(model: @user, local: true) do |f|
  = f.label :name
  = f.text_field :name
  = f.label :email
  = f.text_field :email
  =f.submit '送信'

ここには変化はありません。

フォームオブジェクト

app/forms/form_user_new.rb
class FormUserNew 
  include ActiveModel::Model

  attr_accessor :name, :email

  validates :email, presence: true
  validates :name, presence: true

  def to_model
    User.new(name: name, email: email)
  end

  def save
    return false if invalid?
    to_model.save
  end
end

まず最初にフォームオブジェクトを生み出すフォームクラスなるものはapp/forms/に置いてください。(Rails推奨)

include ActiveModel::Modelによりいつも通りにここでもバリデーションが使えます。
name,email共に空では受け付けないようになっています。
saveメソッドを行う時にバリデーションしてます。

app/forms/form_user_new.rb
 def to_model
   User.new(name: name, email: email)
 end

上の部分ではform_withではモデルに関連したパスを生成するので、ここでフォームオブジェクトをモデル化する時にUserモデルに変換してます。そもそもto_modelはActiveModel内のメソッドです。本来はオブジェクトのクラスのモデルを返しますが、今回それをオーバーライドしてます。

このFromUserNewクラスによって入力されたパラメーターはUserモデルとしてsaveする前にFromUserNewオブジェクトとしてバリデーションがかかり、保存できません。なのでUser.rbに何のバリデーションも記載してないけど、name,emailが空だと保存できません。

まとめ

バリデーションルールが複雑になってくると、modelだけにルールを書くのはよろしくないかと思いまして、フォームオブジェクトでも検証できればコードは綺麗になるかなと。それにフォームオブジェクトでは入力値としての検証!modelではロジック的な検証!と分担できてスッキリします。こゆのって大規模開発になってこないとありがたみってわかないのかもね。明日はモデルの方に移ってカステムバリデートを掘り下げていくンゴ。

参考にしたの

Rails: Form Objectと#to_modelを使ってバリデーションをモデルから分離する(翻訳)
https://techracho.bpsinc.jp/hachi8833/2018_03_02/51350

form objectを使ってみよう
https://tech.medpeer.co.jp/entry/2017/05/09/070758

RailsのFormクラスについて
https://woshidan.hatenablog.com/entry/2018/04/01/221741