Railsバリデーションまとめ

オブジェクトがDBに保存される前に、そのデータが正しいかどうかを検証する仕組みをバリデーションといいますが、
RailsでActiveRecordを使ってそれを実現するにあたってよく使いそうなのをまとめます。

以下のメソッドにおいてはバリデーションがトリガされます。

* create
* create!
* save
* save!
* update
* update!

以下のメソッドにおいてはバリデーションはスキップされます。

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

# Railsでのバリデーショントリガ

Railsでは```valid?```メソッドを実行するとバリデーションが実行されます。
バリデーションが通ればtrueを返し、引っかかればfalseを返します。
ちなみに```invalid?```メソッドは逆の振る舞いをします。

# バリデーションヘルパー

ActiveRecordには多くのバリデーションヘルパーが準備されています。
以下のオプションは全てのヘルパーで使用することが出来ます。

|オプション|概要|
|---|---|
|:allow_nil|対象の値がnilの場合にバリデーションをスキップします。|
|:allow_blank|対象の値が```blank? => true```の場合にバリデーションをスキップします。|
|:on|バリデーションの実行のタイミングを設定できます。:createや:updateを指定するとその場合にのみバリデーションが行われます。|
|:message|エラーメッセージを設定することが出来ます。もし、このオプションがない場合にはデフォルトのメッセージが表示されます。|

以降、用途に応じてバリデーションヘルパーの使用例を掲載します。

## 存在

### 空でないこと

```rb:model
validates :title, presence: true
```

### booleanの場合

```rb:model
validates :completed, inclusion: { in: [true, false] }
```

### チェックボックス

```rb:model
validates :category, acceptance: true
```

### 空であること

```rb:model
validates :login, absence: true
```

## 一意性（ユニーク）

```rb:model
validates :user_name, uniqueness: true
```

## 一致

`指定した属性名`と`#{指定した属性名}_confirmation`を比較します。

```rb:model
validates :email, confirmation: true
```

## 含む、含まない

### inclusion

含むかどうかを検証する場合

```rb:model
validates :kind, inclusion: { in: %w(draft publish private) }
```

### exclusion

含まないことを検証する場合

```rb:model
validates :subdomain, exclusion: { in: %w(www us ca jp) }
```

## 長さ

```rb:model
validates :title,    length: { minimum: 1 }       # 「1文字以上」
validates :title,    length: { maximum: 75 }      # 「75文字以下」
validates :title,    length: { in: 1..75 }        # 「1文字以上75文字以下」
validates :password, length: { is: 8 }            # 「8文字のみ」
```

## フォーマット

## 数値

```rb:model
validates :age, numericality: true
```

またnumericalityには便利なオプションが多数用意されています。

|オプション|概要|
|---|---|
|:only_integer|integerのみ|
|:greater_than|指定された値よりも大きいか|
|:greater_than_or_equal_to |指定された値と等しい、あるいは大きいか|
|:equal_to|指定された値と等しいか|
|:less_than|指定された値よりも小さいか|
|:less_than_or_equal_to|指定された値と等しいか、あるいは小さいか|
|:odd|trueに設定した場合、奇数か|
|:even|trueに設定した場合、偶数か|

## メールアドレス

```rb:model
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, uniqueness: true, format: { with: VALID_EMAIL_REGEX }
```

## 関連

has_manyなどでモデルが関連付けられていて、両方のモデルに対してバリデーションを実行するときに使います。

```rb:model
validates_associated :books
```

> validates_associatedは関連付けの両側のオブジェクトでは実行しないでください。
> 関連付けの両側でこのヘルパーを使用すると無限ループになります。

だそうなのでご注意ください。

## 条件付きのバリデーション

特定の条件の場合にのみバリデーションを有効にしたい場合などがあります。その場合は、:ifや:unlessを使用して条件を設定することができます。

```rb:model
validates :card_number, presence: true, if: :paid_with_card?
 
def paid_with_card?
  payment_type == "card"
end
```

以下のようにProcを使用して書くこともできます。

```rb:model
validates :password, confirmation: true,
    unless: Proc.new { |a| a.password.blank? }
```

条件を複数指定する場合は配列にすることで実現できます。

```rb:model
if: [:is_admin?, "password.blank?"]
```
# カスタムバリデーション

バリデーションヘルパーで実現出来ない場合には自分でメソッドやクラスを作成することができます。

## カスタムメソッド

validateには　```,```　区切りで複数のメソッドを指定することができます。

```rb:model
validate :date_cannot_be_in_the_past
 
def date_cannot_be_in_the_past
  if date.present? && date < Date.today
    errors.add(:date, ": 過去の日付は使用できません")
  end
end
```

## カスタムバリデータ

カスタムバリデータは```ActiveModel::Validator```を拡張したクラスです。作成したクラスでは```validateメソッド```が実装されている必要があり、このメソッドはレコードを1つ引数に取り、それに対してバリデーションを実行します。カスタムバリデータは```validates_withメソッド```を使用して呼び出します。

```rb:model
class TelephoneValidator < ActiveModel::Validator
  def validate(record)
    unless record.tel.starts_with? '0'
      record.errors[:name] << '電話番号は0から始まる必要があります'
    end
  end
end
 
class User
  include ActiveModel::Validations
  validates_with TelephoneValidator
end
```

```ActiveModel:: EachValidator```を拡張すると更に便利に個別の属性を検証することができます。この場合```validate_eachメソッド```を実装する必要があります。このメソッドは、そのインスタンスに対応する「レコードと属性と値」、バリデーションを行なう属性、そして渡されたインスタンスの属性の値の3つの引数を取ります。

```rb:model
class ImageFileTypeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.present?
      file_type = File::extname(value.to_s)
      file_types = %w(.jpg .jpeg .png .gif)
      record.errors[attribute] << I18n.t('errors.messages.invalid_image') unless file_types.include?(file_type)
    end
  end
end

class Image < ActiveRecord::Base
  validates :img, presence: true, image_file_type: true
end
```

# 参考

Rails4でモデルにバリデーションを実装する - Rails Webook
http://ruby-rails.hatenadiary.com/entry/20140724/1406145303
Active Record バリデーション — Rails ガイド
http://railsguides.jp/active_record_validations.html