Tech Racho エンジニアの「?」を「!」に。
  • 開発

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のコールバックで送信しないこと(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。