概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Use a custom validator - Andy Croll
- 原文公開日: 2019/09/08
- 著者: Andy Croll
日本語タイトルは内容に即したものにしました。
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