Rails5 Active Model Validation 検証機能　まとめ

## 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 ガイド](https://railsguides.jp/active_record_validations.html)
#### 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です。

~~~ruby:model
class User < applicationRecord
  validates :terms_of_service, acceptance: true
end
~~~
`terms_of_service`(利用規約)がnil出ない場合にのみ実行されます。このヘルパーのデフォルトエラーメッセージは[must be accepted]です。
##### messageオプションのカスタムが可能
```ruby:model
class User < applicationRecord
  validates :terms_of_service, acceptance: {message: 'must be abided'}
end
```
このヘルパーでは`accept`オプションも渡せます。同意済みとみなす値を指定します。このacceptanceはwebアプリケーション特有のバリデーションであり、データーベースに保存する必要はありません。


```ruby:model
class User < ApplicationRecord
    validates :eura, acceptance: {accept: ['TRUE', 'accepted']}
end
```
#### confirmation
完全一致するものに対して使用します。例えばメールアドレスやパスワードです。

```ruby:model
class User < applicationRecord
  validates :email, confirmation: true
end
```
```ruby:view
<%= text_field :user, :email%>
<%= text_field :user, :email_confirmation%>
```
`:case_sensitive`オプションを用いて、大文字小文字の違いを確認する制約をかけるかどうかも定義できます。<br>

```ruby:model
class Person < ApplicationRecord
  validates :email, confirmation: { case_sensitive: false }
end
```
上記のコードに`uniquiness`をつけることもできます。
大文字小文字を区別しないことにできる。user名ならaaaもAAAも同じユーザー名として扱う事になります。

```ruby:model
class Person < ApplicationRecord
  validates :email, confirmation: uniqueness:{ case_sensitive: false }
end
```
#### exclusion(除外)
与えられた集合に属性の値が含まれていないことを検証します。

```ruby:model
class Account < ApplicationRecord
  validates :subdomain, exclusion: { in: %w(www us ca jp),
    message: "%{value}は予約済みです" }
end
```
デフォルトのエラーメッセージは「is reserved」です。
#### format
withオプションで与えられた正規表現と属性の値がマッチするかどうかのテストによる検証を行います。

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

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

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

```ruby:model
class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
message: "%{value} のサイズは無効です" }
end
```
#### length
このヘルパーは属性の長さを検証します。

```ruby: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にする。
- エラーメッセージは多数あり。

```ruby: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
空でないことを検証します。

```ruby:model 
class User < ApplicationRecord
   validates :name, :login, :email, presence: true
end
```
デフォルトのエラーメッセージは「can't be empty」です。

#### absence 
- 空であることを検証します。
- 値がnilや空文字であるかどうかを検証します。

```ruby:model 
class User < applicationRecord
  validates :name, :login, :email, absence:true
end  
```
関連づけが存在しない場合は

```ruby:model 
class LineItem < ApplicationRecord
  belongs_to :order
  validates :order, absence: true
end
```
関連付けが存在してはならない場合 `inverse_of`オプションで指定します。

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

```ruby:model
class Account < ApplicationRecord
 validates :email, uniqueness: true
end
```
#### validates_with
- validation専用の別のクラスにレコードを渡します。
- バリデーションに使う1つのクラス（またはクラスのリスト）を引数に取ります。
- `validates_with`にはデフォルトのエラーメッセージはありません。
- エラーメッセージが必要な場合は、バリデータクラスのレコードのエラーコレクションに手動で追加する必要があります。
- バリデーションメソッドを実装するには、定義済みのrecordパラメータが必要です。このパラメータはバリデーションを行なうレコードを表します。



```ruby: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`に渡す属性が全てブロックに対してテストされるようにする必要があります。<br>
(例)苗字と名前が小文字で始まらないようにしたいと考えています

```ruby: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をスキップします。

```ruby:model
class Coffee < ApplicationRecord
  validates :size, inclusion: {in:%w(small medium large), message: "%{value}は有効な値ではありません"}, allow_nil: true
end

```
#### allow_blank
- 属性の値が`blank?`に該当する場合、nilや空文字にvalidationがパスします。

```ruby: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}は最大値や最小値などのパラメーターで、利用できるのは`length`や`numericality`などに限定されます。

```ruby: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時になります。

```ruby: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`オプションに指定することができる。

#### 文字列で条件式を指定

```ruby:model
class User < ApplicationRecord
 validates :email, presence: {unless: 'dm.blank?'}
end
```
#### シンボルで指定します
- シンボルに対応するメソッドを定義します。

```ruby:model
class User < ApplicationRecord
  validates :email, presence: {unless: :sendmail?}

  def sendmail?
   dm.blank?
  end
end
```

#### Procオブジェクトで指定します
- 引数として現在のモデルオブジェクトが渡されますので、これを元に処理を記述します。

```ruby:model
class User < ApplicationRecord
 validates :email,
   presence: {unless: Proc.new { |U|u.dm.blank? }}
end
```

```ruby: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`メソッドで呼び出す

```ruby: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をスキップします。<br>
すでに検証済みであるか、あらかじめ信頼できる値である事がわかっている場合にのみ利用します。

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

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

~~~ruby
save(validate: false)
~~~

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

- `valid?`(検証okですか？)がトリガされオブジェクトにエラーがなければtrueが返されます。そうでなければfalseを返します。

```ruby
class User < applicationRecord
  validates : name,
  presence: true
end
user.create(name: "Tarou").valid? # => true
user.create(name: nil).valid? #=> false
```
2. `errors.messages`インスタンスメソッド
- 発生したエラーにアクセスできます。
- エラーのコレクションを返します。<br>

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

```ruby
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`でエラーにアクセスしても、空です。<br>

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

```ruby
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をハッシュ配列で返します
(例)

```ruby 
class User < ApplicationRecord
  validates :name, presence: true
end

user = User.new
user.valid?
user.errors.detailes[:name] # => [{error: :blank}]
```
### まとめ
validationの基礎的なものをまとめました。本題は自作検証クラスであったりすると思いますが
まずは多岐にわたるvalidationの数々を使いこなせるようになりたいです。