Rails: バリデーターの直書きを避けてカスタムバリデーターを作ろう(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails: バリデーターの直書きを避けてカスタムバリデーターを作ろう(翻訳)

モデル内のvalidates呼び出しで使われるActive Modelのバリデーションにはさまざまなオプションが用意されており、これを用いて独自のクラスを強化できます。Railsガイドにも6.1 カスタムバリデータというセクションがあります。

以下のように書かないこと

validates呼び出しにコードをごちゃごちゃ書く。

class Invite
  validates :invitee_email, format: {
    with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i,
    message: "is not an email"
  }
end

以下のように書くこと

バリデータークラスを作ってロジックをそこに切り出す。

# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end
class Invite
  # バリデーション名はRailsのマジックが推測してくれる
  validates :invitee_email, email: true
end

そうする理由

バリデーションロジックを再利用する可能性がある場合や、バリデーションの書式設定ルールが複雑な場合は、その機能を専用のオブジェクトに切り出すのがよい方法です。

これならロジックを切り離してテストできますし、ロジックが使われるときの動作もわかりやすくなります。

そうしない理由があるとすれば?

特にメールのバリデーションの場合、ややトリッキーなバリデーションを書いたことがあります。シンプルだったのでバリデーターも要らないぐらいだったのですが、アプリの認証でdeviseを使う必要が生じたのです。

class Invite
  validates :invitee_email, format: {
    with: Devise.email_regexp,
    allow_blank: false
  }
end

そういう場合は以下の書き方もありです。

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ Devise.email_regexp
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end

関連記事

Rails: メールをActive Recordのコールバックで送信しないこと(翻訳)

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

夏のTechRachoフェア2019

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ