はじめに
株式会社ピー・アール・オーのアドベントカレンダー18日目を書きます。
個人的にRuby on Railsに触れる機会があったのでRailsのバリデーションについて書きたいと思います。
ちなみに弊社ではRubyの案件は自分の知る限りほとんどありません。PHP、Javaの案件が多い印象です。
だいぶ遅まきで、勉強し始めているのですがRails初学者の私からするとRailsのコードを見たときの第一印象はコード量が少ないでした。
コード量が少ないということは保守性に優れたコードであるということなのですが、
Railsを何も知らない人からすると逆にこれってどうやって動いてるの?となってしまうわけです。
新しく触れるフレームワークでは、いつもあることでお作法をよく理解することが一番だと思います。
お作法を理解するには先ずはRailsガイドよく読むのがよさそうでした。
ガイドを読んでいる中で割と最初の方に出てくるバリデーションの章が印象に残っているのでいくつか紹介したいと思います。
Railsではバリデーション機能をバリデーションヘルパーという形で提供してくれており、
少ないコード量でバリデーションチェックができるのでとても便利です。
(どのように実装するかなどの詳細は割愛します)
2.4 comparison
このチェックは、比較可能な2つの値の比較を検証します。 比較オプションの指定が必要です。各オプションには値、> procまたはシンボルを渡せます。Comparableをincludeするクラスはすべて比較可能です。
class Promotion < ApplicationRecord
  validates :end_date, comparison: { greater_than: :start_date }
end
サンプルコードのとおり開始日、終了日のチェックに使えます。
2.6 format
withオプションで与えられた正規表現と属性の値がマッチするかどうかを検証します。
class Product < ApplicationRecord
  validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
    message: "英文字のみが使えます" }
end
正規表現の行頭は通常、キャレット(^)、行末はダラー($)を使いますが、
Railsのヘルパーでは \A \z と書くようですね。
メールフォーマットのチェック、URLチェックなどが代表的な用途でしょうか。
2.9 numericality
属性に数値のみが使われていることを検証します。デフォルトでは、整数値または浮動小数点数値にマッチします。
class Player < ApplicationRecord
  validates :games_played, numericality: { greater_than_or_equal_to: 0 }
end
このヘルパーにはいろいろオプションがあるのですが:greater_than_or_equal_toで値が0以上であることという数値チェックができます。
金額などのチェックに使えそうです。
2.12 uniqueness
オブジェクトが保存される直前に、属性の値が一意(unique)であり重複していないことを検証します。
class Account < ApplicationRecord
  validates :email, uniqueness: true
end
こちらのヘルパーですが重複チェックがワンライナーで書けてものすごい便利だなと思って使ってみたのですが更新処理でも自身の値が重複扱いされてしまい自分が思い描いていた通りのチェックができませんでした。
これを解決する手段としてガイドを読み進めると:onという便利そうなオプションがありました。
3.4 :on
バリデーション実行のタイミングを指定します。
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?の時もバリデーションされるし、レコード新規作成時、更新時にもバリデーションしてくれます。
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に触れるきっかけになる記事になれば幸いです。

