Ruby on Rails のバリデーション

## はじめに
株式会社ピー・アール・オーのアドベントカレンダー18日目を書きます。
個人的にRuby on Railsに触れる機会があったのでRailsのバリデーションについて書きたいと思います。
ちなみに弊社ではRubyの案件は自分の知る限りほとんどありません。PHP、Javaの案件が多い印象です。

だいぶ遅まきで、勉強し始めているのですがRails初学者の私からするとRailsのコードを見たときの第一印象は**コード量が少ない**でした。
コード量が少ないということは保守性に優れたコードであるということなのですが、
Railsを何も知らない人からすると逆にこれってどうやって動いてるの？となってしまうわけです。
新しく触れるフレームワークでは、いつもあることでお作法をよく理解することが一番だと思います。

お作法を理解するには先ずはRailsガイドよく読むのがよさそうでした。

ガイドを読んでいる中で割と最初の方に出てくるバリデーションの章が印象に残っているのでいくつか紹介したいと思います。

https://railsguides.jp/active_record_validations.html

Railsではバリデーション機能をバリデーションヘルパーという形で提供してくれており、
少ないコード量でバリデーションチェックができるのでとても便利です。
（どのように実装するかなどの詳細は割愛します）

## 2.4 comparison

> このチェックは、比較可能な2つの値の比較を検証します。 比較オプションの指定が必要です。各オプションには値、> procまたはシンボルを渡せます。Comparableをincludeするクラスはすべて比較可能です。

```ruby:sample.rb
class Promotion < ApplicationRecord
  validates :end_date, comparison: { greater_than: :start_date }
end
```

サンプルコードのとおり開始日、終了日のチェックに使えます。

## 2.6 format

> withオプションで与えられた正規表現と属性の値がマッチするかどうかを検証します。

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

正規表現の行頭は通常、キャレット（^）、行末はダラー（$）を使いますが、
Railsのヘルパーでは　\A　\z　と書くようですね。
メールフォーマットのチェック、URLチェックなどが代表的な用途でしょうか。

## 2.9 numericality

> 属性に数値のみが使われていることを検証します。デフォルトでは、整数値または浮動小数点数値にマッチします。

```ruby:sample.rb
class Player < ApplicationRecord
  validates :games_played, numericality: { greater_than_or_equal_to: 0 }
end
```

このヘルパーにはいろいろオプションがあるのですが`:greater_than_or_equal_to`で値が0以上であることという数値チェックができます。
金額などのチェックに使えそうです。

## 2.12 uniqueness

> オブジェクトが保存される直前に、属性の値が一意（unique）であり重複していないことを検証します。

```ruby:sample.rb
class Account < ApplicationRecord
  validates :email, uniqueness: true
end
```

こちらのヘルパーですが重複チェックがワンライナーで書けてものすごい便利だなと思って使ってみたのですが更新処理でも自身の値が重複扱いされてしまい自分が思い描いていた通りのチェックができませんでした。

これを解決する手段としてガイドを読み進めると`:on`という便利そうなオプションがありました。

## 3.4 :on

> バリデーション実行のタイミングを指定します。

```ruby:sample.rb
class Person < ApplicationRecord
  # 値が重複していてもemailを更新できる
  validates :email, uniqueness: true, on: :create

  # 新規レコード作成時に、数字でない年齢表現を使える
  validates :age, numericality: true, on: :update

  # デフォルト (作成時と更新時の両方でバリデーションを行なう)
  validates :name, presence: true
end
```

`on: :create`を指定すればレコード新規作成時にのみバリデーションが行われるということで、これを使えば良さそうです。

が、、、
確認画面を挟んでバリデーションをかけたいときに `valid?` や `invalid?`などを使うと思いますが`on: :create`のオプションを使っているとレコード新規作成時しかチェックしないのでスキップされてしまいました。
また、更新処理のとき変更しようとした値が既存データと重複していないかもチェックをしたいところなので`uniqueness:`では実現が難しそうです。

## 6.2 カスタムメソッド

最終的にどのようにしたのかというとカスタムメソッドで重複チェックのロジックを書いて解決することにしました。`validate`にてカスタムメソッドを呼び出すと、`valid?` や `invalid?`の時もバリデーションされるし、レコード新規作成時、更新時にもバリデーションしてくれます。

```ruby:sample.rb
class Person < ApplicationRecord
  # uniquenessでのチェックはやめる
  # validates :email, uniqueness: true, on: :create

　# カスタムメソッドの重複チェックを呼び出す
  validate :validate_duplicate

  def validate_duplicate
    # email 重複チェック
    person = Person.find_by(email: self.email)
    if person.present?
      unless self.id.present?
        # 新規
        errors.add(:email, "が重複しています。違う値を指定してください。")  
      else
        # 更新
        unless person.id == self.id
          errors.add(:email, "が重複しています。違う値を指定してください。")  
        end
      end
    end
  end
end
```

以上、Ruby on Railsのバリデーションについての紹介でした。
Railsに触れるきっかけになる記事になれば幸いです。