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 ガイドの見解)
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です。
class User < applicationRecord
validates :terms_of_service, acceptance: true
end
terms_of_service
(利用規約)がnil出ない場合にのみ実行されます。このヘルパーのデフォルトエラーメッセージは[must be accepted]です。
messageオプションのカスタムが可能
class User < applicationRecord
validates :terms_of_service, acceptance: {message: 'must be abided'}
end
このヘルパーではaccept
オプションも渡せます。同意済みとみなす値を指定します。このacceptanceはwebアプリケーション特有のバリデーションであり、データーベースに保存する必要はありません。
class User < ApplicationRecord
validates :eura, acceptance: {accept: ['TRUE', 'accepted']}
end
confirmation
完全一致するものに対して使用します。例えばメールアドレスやパスワードです。
class User < applicationRecord
validates :email, confirmation: true
end
<%= text_field :user, :email%>
<%= text_field :user, :email_confirmation%>
:case_sensitive
オプションを用いて、大文字小文字の違いを確認する制約をかけるかどうかも定義できます。
class Person < ApplicationRecord
validates :email, confirmation: { case_sensitive: false }
end
上記のコードにuniquiness
をつけることもできます。
大文字小文字を区別しないことにできる。user名ならaaaもAAAも同じユーザー名として扱う事になります。
class Person < ApplicationRecord
validates :email, confirmation: uniqueness:{ case_sensitive: false }
end
exclusion(除外)
与えられた集合に属性の値が含まれていないことを検証します。
class Account < ApplicationRecord
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value}は予約済みです" }
end
デフォルトのエラーメッセージは「is reserved」です。
format
withオプションで与えられた正規表現と属性の値がマッチするかどうかのテストによる検証を行います。
class Product < ApplicationRecord
validates :legacy_code, format: {with: /\A[a-zA-z]+\z/, message: "英文字のみが使用できます"}
end
デフォルトのエラーメッセージは「in valid」です。
inclusion
含まれているかどうか検証します。
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} のサイズは無効です" }
end
length
このヘルパーは属性の長さを検証します。
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にする。
- エラーメッセージは多数あり。
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
空でないことを検証します。
class User < ApplicationRecord
validates :name, :login, :email, presence: true
end
デフォルトのエラーメッセージは「can't be empty」です。
absence
- 空であることを検証します。
- 値がnilや空文字であるかどうかを検証します。
class User < applicationRecord
validates :name, :login, :email, absence:true
end
関連づけが存在しない場合は
class LineItem < ApplicationRecord
belongs_to :order
validates :order, absence: true
end
関連付けが存在してはならない場合 inverse_of
オプションで指定します。
class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
uniqueness
- オブジェクトが保存される直前に、属性の値が一意であり、重複していることを検証します。
- このヘルパーは一意性の制約をデータベース自体には作成しないので、本来一意にすべきカラムに、たまたま2つのデータベース接続によって同じ値を持つレコードが2つ作成される可能性が残ります。これを避けるには、データベースの両方のカラムに一意インデックスを作成する必要があります。
class Account < ApplicationRecord
validates :email, uniqueness: true
end
validates_with
- validation専用の別のクラスにレコードを渡します。
- バリデーションに使う1つのクラス(またはクラスのリスト)を引数に取ります。
-
validates_with
にはデフォルトのエラーメッセージはありません。 - エラーメッセージが必要な場合は、バリデータクラスのレコードのエラーコレクションに手動で追加する必要があります。
- バリデーションメソッドを実装するには、定義済みのrecordパラメータが必要です。このパラメータはバリデーションを行なうレコードを表します。
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
に渡す属性が全てブロックに対してテストされるようにする必要があります。
(例)苗字と名前が小文字で始まらないようにしたいと考えています
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をスキップします。
class Coffee < ApplicationRecord
validates :size, inclusion: {in:%w(small medium large), message: "%{value}は有効な値ではありません"}, allow_nil: true
end
allow_blank
- 属性の値が
blank?
に該当する場合、nilや空文字にvalidationがパスします。
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}は最大値や最小値などのパラメーターで、利用できるのは
length
やnumericality
などに限定されます。
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時になります。
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
オプションに指定することができる。
文字列で条件式を指定
class User < ApplicationRecord
validates :email, presence: {unless: 'dm.blank?'}
end
シンボルで指定します
- シンボルに対応するメソッドを定義します。
class User < ApplicationRecord
validates :email, presence: {unless: :sendmail?}
def sendmail?
dm.blank?
end
end
Procオブジェクトで指定します
- 引数として現在のモデルオブジェクトが渡されますので、これを元に処理を記述します。
class User < ApplicationRecord
validates :email,
presence: {unless: Proc.new { |U|u.dm.blank? }}
end
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
メソッドで呼び出す
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を実行します。エラーが発生すると保存しません。
-
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
-
errors.messages
インスタンスメソッド
- 発生したエラーにアクセスできます。
- エラーのコレクションを返します。
⚠️newでインスタンスされたオブジェクトは、エラーが出力されません。あくまでも、create
やsave
メソッドなどで、オブジェクトが保存される時のみ実行されるためです。
️(例)
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の数々を使いこなせるようになりたいです。