LoginSignup
27
24

More than 3 years have passed since last update.

Rails5 Active Model Validation 検証機能 まとめ

Last updated at Posted at 2019-07-03

Rails5 Active Model Validation 検証機能 まとめ

エンドユーザーから入力された値は、まず正しくないことを前提にアプリは実装されるべきです。山田 祥寛さんの著書 Ruby On Rails 5 Application Programmingの一節です。正しいデータをdbに保存するための、モデルの状態を検証できるvalidationといった検証機能についてまとめたいと思います。

目次
- validationを行う理由
- モデル以外のvalidationの実装(Rails ガイドの見解)
- Active Model::Validationという機能が持つヘルパーメソッドとパラメーター
- 共通のバリデーション
- 条件付きのvalidation
- カスタムバリデータ
- メソッドにはvalidationをトリガーするものと、しないもの
- valid? と invalid?

validationを行う理由

  • 正しいデータだけをデータベースに保存するために行います。
  • データーベースに依存せず、モデルレベルでvalidationを実行するのが最適。
  • エンドユーザーがバイパスすることも出来ず、テストもメンテナンスもやりやすい。
  • validationを簡単に扱える様に、一般に利用可能なビルトインヘルパーが用意されています。
  • 独自のvalidationメソッドも作成できます。

モデル以外のvalidationの実装(Rails ガイドの見解)

Rails ガイド

dbに依存したvalidation

  • db制約
  • ストアドプロシージャ

テストや保守がその分面倒になります

クライアント側でのvalidation

  • jqueryでのvalidationの実装
  • JavaScriptを使ってバリデーションを実装する場合、ユーザーがブラウザー側でJavaScriptをオフにしてしまえばバイパスされてしまいます。

一般に単独では信頼性が不足します

コントローラレベルでのvalidation

  • テストも保守も困難になりがちです

アプリケーションの寿命を永らえ、保守作業を苦痛なものにしないようにするためには、コントローラのコード量は可能な限り減らすべき

このような見解であれば、modelでのvalidationを知っておく事が必須の様に思います。

Active Model::Validationという機能が持つヘルパーメソッドとパラメーター

  • 定義済みのバリデーションヘルパーが用意されています。
  • バリデーションルールを提供します。
  • バリデーションが失敗するたびに、オブジェクトのerrorsコレクションにエラーメッセージが追加されます。
検証ルール(必須検証) 概要
一意検証 同じであること
文字列長検証 文字の長さ
正規表現検証 正規表現パターンに合致しているか
数値検証 整数であることや数値の範囲
候補値検証 データの候補

その他あり

検証名 検証内容 /パラメーター エラーメッセージ
acceptanse チェックボックスにチェックがあるか /accept must be accepted
confirmation 二つのフィールドが正しいか does't much confirmation
exclusion 与えられた集合に属性の値が含まれていないかどうか/ in is reserved
inclusion 含まれているかどうか/ in is not included in the list
format 正規表現パターンに合致しているかどうか/ with is invalid
length 文字列の長さ / minimum/maxmum/その他 is too short
numericality 数値の大小や型/ only_integer/less_than/その他  is not a number
presence 値が空でないかどうか/ - can't be empty
absence 値が空であるか must be blank
uniqueness 値が一意であるか/ scope/case_sensitive has already been taken

:onと:messageオプションはどのヘルパーでも使えます

acceptance

フォームが送信された時にユーザーインターフェイス上のチェックボックスがオンになっているかどうかを検証します。よくある利用規約のチェックボックスのvalidationです。

model
class User < applicationRecord
  validates :terms_of_service, acceptance: true
end

terms_of_service(利用規約)がnil出ない場合にのみ実行されます。このヘルパーのデフォルトエラーメッセージは[must be accepted]です。

messageオプションのカスタムが可能
model
class User < applicationRecord
  validates :terms_of_service, acceptance: {message: 'must be abided'}
end

このヘルパーではacceptオプションも渡せます。同意済みとみなす値を指定します。このacceptanceはwebアプリケーション特有のバリデーションであり、データーベースに保存する必要はありません。

model
class User < ApplicationRecord
    validates :eura, acceptance: {accept: ['TRUE', 'accepted']}
end

confirmation

完全一致するものに対して使用します。例えばメールアドレスやパスワードです。

model
class User < applicationRecord
  validates :email, confirmation: true
end
view
<%= text_field :user, :email%>
<%= text_field :user, :email_confirmation%>

:case_sensitiveオプションを用いて、大文字小文字の違いを確認する制約をかけるかどうかも定義できます。

model
class Person < ApplicationRecord
  validates :email, confirmation: { case_sensitive: false }
end

上記のコードにuniquinessをつけることもできます。
大文字小文字を区別しないことにできる。user名ならaaaもAAAも同じユーザー名として扱う事になります。

model
class Person < ApplicationRecord
  validates :email, confirmation: uniqueness:{ case_sensitive: false }
end

exclusion(除外)

与えられた集合に属性の値が含まれていないことを検証します。

model
class Account < ApplicationRecord
  validates :subdomain, exclusion: { in: %w(www us ca jp),
    message: "%{value}は予約済みです" }
end

デフォルトのエラーメッセージは「is reserved」です。

format

withオプションで与えられた正規表現と属性の値がマッチするかどうかのテストによる検証を行います。

model
class Product < ApplicationRecord
  validates :legacy_code, format: {with: /\A[a-zA-z]+\z/, message: "英文字のみが使用できます"}
end

デフォルトのエラーメッセージは「in valid」です。

inclusion

含まれているかどうか検証します。

model
class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
message: "%{value} のサイズは無効です" }
end

length

このヘルパーは属性の長さを検証します。

model
class User < ApplicationRecord
  validates :name, length: {minimum: 2}
  validates :bio, length: {maximum: 500}
  validates :password, length: {in :6..20}
  validates :registration_number, lenght: {is: 6}
end

使用可能な長さ制限オプションは以下の通りです。

:minimum: 最小の文字列長
:maximum: 最大の文字列長
:tokenizer 文字列の分割方法
:inまたは:within: 文字列長の範囲。
:is :属性の長さの一致
: too_long :error message
:too_short :error message
:wrong_length :is パラメーターに違反した時のerror

numericality(数値)

  • 数値のみが使われているか検証します。
  • 整数のみにマッチさせたい場合は:only_integerをtrueにする。
  • エラーメッセージは多数あり。
model
class Player < ApplicationRecord
  validates :points, numericality: true
  validates :games_played, numericality: {only_integer: true}
end
パラメーター 意味
only_integer 整数であるか
greater_than 指定値より大きいか
greater_than_or_equal_to 指定値以上か
equal_to 指定値と等しいか
less_than 指定値未満か
less_than_or_equal_to 指定値以下か
odd 奇数か
even 偶数か

presence

空でないことを検証します。

model
class User < ApplicationRecord
   validates :name, :login, :email, presence: true
end

デフォルトのエラーメッセージは「can't be empty」です。

absence

  • 空であることを検証します。
  • 値がnilや空文字であるかどうかを検証します。
model
class User < applicationRecord
  validates :name, :login, :email, absence:true
end  

関連づけが存在しない場合は

model
class LineItem < ApplicationRecord
  belongs_to :order
  validates :order, absence: true
end

関連付けが存在してはならない場合 inverse_ofオプションで指定します。

model
class Order < ApplicationRecord
  has_many :line_items, inverse_of: :order
end

uniqueness

  • オブジェクトが保存される直前に、属性の値が一意であり、重複していることを検証します。
  • このヘルパーは一意性の制約をデータベース自体には作成しないので、本来一意にすべきカラムに、たまたま2つのデータベース接続によって同じ値を持つレコードが2つ作成される可能性が残ります。これを避けるには、データベースの両方のカラムに一意インデックスを作成する必要があります。
model
class Account < ApplicationRecord
 validates :email, uniqueness: true
end

validates_with

  • validation専用の別のクラスにレコードを渡します。
  • バリデーションに使う1つのクラス(またはクラスのリスト)を引数に取ります。
  • validates_withにはデフォルトのエラーメッセージはありません。
  • エラーメッセージが必要な場合は、バリデータクラスのレコードのエラーコレクションに手動で追加する必要があります。
  • バリデーションメソッドを実装するには、定義済みのrecordパラメータが必要です。このパラメータはバリデーションを行なうレコードを表します。
model
class GoodnessValidator < ActiveModel::Validator
  def validatate(record)
  if record.first_name == "Evil"
    record.errors[:base] << "これは悪人だ"
  end
 end
end

class Person < applicationRecord
  validates_with GoodnessValidator
end

validates_each

  • 1つのブロックに対して属性の検証します。定義済みのバリデーション関数はありません。
  • ブロックで使うバリデーションを自分で作成し、validates_eachに渡す属性が全てブロックに対してテストされるようにする必要があります。
    (例)苗字と名前が小文字で始まらないようにしたいと考えています
model
class Person < ApplicationRecord
  validates_each :name, :surname do | record, attr, value |
    record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
  end
end

このブロックは、レコードと属性の名前、そして属性の値を受け取ります

共通のバリデーション

共通で使えるvalidation option

:allow_nil

  • 任意入力項目で全ての検証が実行されてしまうのは望ましくありません。
  • パラメーターを有効(true)にしておく事で対象の項目がからである場合に検証をスキップさせる事ができます。
  • :allow_nilオプションは、対象の値がnilの場合にvalidationをスキップします。
model
class Coffee < ApplicationRecord
  validates :size, inclusion: {in:%w(small medium large), message: "%{value}は有効な値ではありません"}, allow_nil: true
end

allow_blank

  • 属性の値がblank?に該当する場合、nilや空文字にvalidationがパスします。
model
class Topic < ApplicationRecord
  validates :title, length: {is: 5}, allow_blank: true
end
topic.create(title: "").valid? # => true
topic.create(title: nil),.valid? # => true

:message

  • validation失敗時にerrorsコレクションに追加されるカスタムエラーメッセージを指定します。
  • messageパラメーターには、%{value}や%{count}の様な形式でプレイスホルダーを埋め込む事ができます。
  • %{value}や%{count}は最大値や最小値などのパラメーターで、利用できるのはlengthnumericalityなどに限定されます。
model
class Person < ApplicaitonRecord
  validates :name, presence:{message: "must be given please"}
  validates :age, numericality: {message: "%{value} seems wrong"
  }
end

:on

  • ビルトインのバリデーションヘルパーは、デフォルトでは保存時に実行されます。
  • validationのタイミングを変更したい時、on: :createを指定します。
  • on: :updateも同じで、検証のタイミングはupdate時になります。
model
class Person < applicationRecord
  #重複していても、新規作成可能
  validates :email, uniqueness: true, on: :create
  #更新時に数値でない表現を使える
  validates :age, numericality: true, on: :update
  #名前欄がからでないなら
  validates :name, presence: true
end

条件付きのvalidation

  • 特定の条件を満たす場合にのみvalidationをきかせます。
  • :ifオプションや:unlessオプションを使うことで指定します。
  • :if は特定の条件でvalidationで行うべきである場合に使います。
  • :unlessは特定の条件でvalidationを使うべきでない場合に使用します。
方法
  • :if:unslessでシンボルを使う
  • validationの実行前に呼び出されるメソッド名をシンボルで:if:unlessオプションに指定することができる。

文字列で条件式を指定

model
class User < ApplicationRecord
 validates :email, presence: {unless: 'dm.blank?'}
end

シンボルで指定します

  • シンボルに対応するメソッドを定義します。
model
class User < ApplicationRecord
  validates :email, presence: {unless: :sendmail?}

  def sendmail?
   dm.blank?
  end
end

Procオブジェクトで指定します

  • 引数として現在のモデルオブジェクトが渡されますので、これを元に処理を記述します。
model
class User < ApplicationRecord
 validates :email,
   presence: {unless: Proc.new { |U|u.dm.blank? }}
end
model
class Order < AapplicationRecord
    validates :card_number, presence: true, if: :paid_with_card?

    def paid_with_card?
      payment_type == "card"
    end
end

カスタムvalidation

  • 自作のvalidateやvalidationメソッドを作成できる

カスタムバリデータ

  • validatorはActiveModel::Validatorを継承するクラス
  • これらのクラスではvalidatorメソッドを実装する必要がある
  • レコードを一つ引数にとる
  • それに対してvalidationを実行する
  • カスタムvalidateはvalidates_withメソッドで呼び出す
model
class MyValidator < ActiveModel::Validator
  def validatate(record)
    unless record.name.starts_with? 'x'
      record.errors[:name] << '名前はxで始まる必要があります'
    end
  end
end

class Person 
    include ActiveModel ::Validations
    validates_with MyValidator
end

メソッドにはvalidationをトリガーするものと、しないものがある

以下のメソッドはバリデーションがトリガーされ、オブジェクトが有効な場合にのみデーターベースに保存されます。
- create
- create!
- save
- save!
- update
- update!

以下のメソッドはばvalidationをスキップします。

すでに検証済みであるか、あらかじめ信頼できる値である事がわかっている場合にのみ利用します。

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

実はsaveにvalidate: false を引数として与えるとsaveのvalidationをスキップします。

save(validate: false)

valid? と invalid?

オブジェクトを保存する直前にvalidationを実行します。エラーが発生すると保存しません。
1. valid?メソッドで手動でトリガーする

  • valid?(検証okですか?)がトリガされオブジェクトにエラーがなければtrueが返されます。そうでなければfalseを返します。
class User < applicationRecord
  validates : name,
  presence: true
end
user.create(name: "Tarou").valid? # => true
user.create(name: nil).valid? #=> false
  1. errors.messagesインスタンスメソッド
  2. 発生したエラーにアクセスできます。
  3. エラーのコレクションを返します。

⚠️newでインスタンスされたオブジェクトは、エラーが出力されません。あくまでも、createsaveメソッドなどで、オブジェクトが保存される時のみ実行されるためです。
️(例)

class User < ApplicationRecorde
  validates :name, presence: true
end

#ここから
user = User.new
# => #<User id: nil, name: nil>
user.errors.messages
# => {}

user.valid?
# => false
user.errors.messages
# => {name:["空欄には出来ません"]}

user = User.create
# => {name: ["空欄には出来ません"]}
user.errors.messages
# => {name:["空欄には出来ません"]}

user.save
# => false

user.save!
# => ActiveRecord::RecordInvalid: Validation failed: 空欄には出来ません

user.create!
# => ActiveRecord::RecordInvalid: Validation failed: 空欄には出来ません

user = User.newによる保存はされていないので、user.errors.messagesでエラーにアクセスしても、空です。

errors[]

  • errors[:attribute]を使うと、特定のオブジェクトの属性が有効かどうか確認できます。
  • :attributeの全てのエラーの配列を返す指定された属性でエラーが発生しない場合は、からの配列が返されます。
  • このメソッドはオブジェクト全体の正当性については確認しません。
  • エラーのコレクションを調べるだけで、validationそのものをトリガしないからです。
  • オブジェクトの個別の属性についてエラーがあるかどうかを調べます。
    (例)
class User < ApplicationRecord
  validates :name, presence: true
end

User.new.errors[:name].any? # => false
User.create.errors[:name].any? # => true

errors.details

  • 無効(invalid)な属性においてどのvalidationが失敗したのかを調べるのにerrors.details[:attribute]が利用できます。
  • 失敗したvalidateをハッシュ配列で返します (例)
class User < ApplicationRecord
  validates :name, presence: true
end

user = User.new
user.valid?
user.errors.detailes[:name] # => [{error: :blank}]

まとめ

validationの基礎的なものをまとめました。本題は自作検証クラスであったりすると思いますが
まずは多岐にわたるvalidationの数々を使いこなせるようになりたいです。

27
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
24