Rails のメールアドレスバリデーション

いくつかの Rails プロジェクトのメールアドレスのバリデートは、正規表現ではなく mail gem と treetop gem を組み合わせて 行なっている。

  • Mail::Address.new(address) でアドレスをパースできる
  • パース結果にドメイン部分が存在し、パース結果のアドレスが同一である
  • Treetop で解析したドメイン部分のエレメントが example.com のように 2 つ以上

しかし、mail gem のバージョン 2.6.0 から treetop gem が依存関係から削除されたため、ドメイン部分のエレメントの検証が出来なくなった。

そこで、以下のように処理を変更した。

require 'mail'
require 'resolv'

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    begin
      m = Mail::Address.new(value)
      r = m.domain && m.address == value

      mx = []
      Resolv::DNS.open do |dns|
        domain = m.domain.downcase

        mx = dns.getresources(domain, Resolv::DNS::Resource::IN::MX) +
          dns.getresources(domain, Resolv::DNS::Resource::IN::A)
      end

      r &&= mx.size > 0
    rescue Exception
      r = false
    end

    record.errors[attribute] << (options[:message] || I18n.t('activerecord.errors.messages.invalid')) unless r
  end
end
  • メールアドレスのパースと結果の検証は今まで通りで、
  • ドメイン部分の DNS を引いて MX レコードまたは A レコードが存在するか検証

これらをすべて満たした場合に、バリデーションをパスできる。 (spec 実行時にインターネットへアクセスが走るので工夫する必要がある。)


メールアドレスに厳しめのバリデーションをかけて、ユーザーが登録できなくて残念な思いをするぐらいなら、 スペルミスを教えてあげたりした方が幸せになれるのかな、と思う。

例えば mailcheck.js を使うと、フォーム上でスペルミスがある場合にサジェストしてくれる。


ちなみに、DNS を引く処理については、メールアドレスのバリデーションを行う gem である validates_email_format_of gem から拝借した。

この gem は厳しめのバリデーションがかかるので、キャリアメールが使われることの多い(?)日本のサービスで使用するのは厳しいかもしれないが、それが問題なければ導入は簡単だしメンテナンスもされているので良い選択肢かなと思う。

Rails のメールアドレスバリデーション、皆さんどうやっているのでしょうか。

© 2023 暇人じゃない. All rights reserved.