Railsの電子メール検証における最新技術とは何ですか?


95

ユーザーのメールアドレスを検証するために何を使用していますか、そしてその理由は何ですか?

私はvalidates_email_veracity_of実際にMXサーバーを照会するを使用していました。しかし、ネットワークトラフィックと信頼性に関連するさまざまな理由により、それは失敗に満ちています。

私は周りを見回しましたが、多くの人が電子メールアドレスの健全性チェックを実行するために使用していることは明らかでした。これのために維持された、適度に正確なプラグインまたは宝石はありますか?

PS:メールが機能するかどうかを確認するためのリンクを記載したメールを送信しないでください。「友達に送る」機能を開発しているので、これは実用的ではありません。


ここで、超簡単な方法は、正規表現を扱うずに、次のとおりです--有効-電子メールアドレスを検出
Zabba

MXサーバーへのクエリが失敗する理由を詳しく教えてください。これらが修正可能かどうかを確認できるようにしたいと思います。
lulalala 2013年

回答:


67

Rails 3.0では、Mail gemを使用して正規表現なしでメール検証を使用できます。

これが私の実装ですgemとしてパッケージ化されています)。


いいですね、あなたの宝石を使っています。ありがとう。
jasoncrawford

###@domain.com検証するように見えますか?
cwd

1
みんな私はこの宝石を復活させたいのですが、それを維持する時間がありませんでした。しかし、人々はまだそれを使用しており、改善を求めているようです。ハレルヤ/ VALID_EMAIL:興味のある方は、githubのプロジェクトに私を書いてください
ハレルヤ

106

これを必要以上に難しくしないでください。あなたの機能は重要ではありません。検証は、タイプミスを検出するための基本的な正気ステップです。私は単純な正規表現でそれを行い、あまりにも複雑なものでCPUサイクルを無駄にしないでください:

/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/

これは、http://www.regular-expressions.info/email.htmlから改作されたものです。すべてのトレードオフを本当に知りたい場合は、このドキュメントをお読みください。より正確ではるかに複雑で完全にRFC822に準拠した正規表現が必要な場合は、そのページにも記載されています。しかし、問題はこれです。完全に正しくする必要はありません。

アドレスが検証に合格すると、メールが送信されます。電子メールが失敗すると、エラーメッセージが表示されます。どの時点でユーザーに「申し訳ありませんが、あなたの友人はそれを受け取っていません。もう一度やり直しますか?」と伝えることができます。または手動で確認するためにフラグを立てるか、単に無視するかなど。

これらは、住所検証に合格した場合に対処する必要があるオプションと同じです。検証が完璧で、アドレスが存在するという絶対的な証拠を取得したとしても、送信は失敗する可能性があるためです。

検証の誤検知のコストは低いです。より良い検証の利点も低いです。寛大に検証し、エラーが発生したときに心配します。


36
えっと、.museumと新しい国際TLDについての議論はありませんか?この正規表現は多くの有効な電子メールアドレスを妨害します。
エリヤ

3
エリヤに同意する、これは悪い推薦です。さらに、メールがすぐに成功したかどうかを判断する方法がないため、友達にメールが届かなかったとユーザーにどのように伝えることができるかはわかりません。
Jaryl、

8
.museumなどの良い点-2009年にその回答を最初に投稿したときは問題ではなかった。正規表現を変更しました。さらに改善がある場合は、それを編集するか、これをコミュニティーWiki投稿にすることができます。
SFEley、2011年

5
参考までに、これはまだいくつかの有効なメールアドレスを逃します。多くはないが、少数。たとえば、技術的には#|@foo.comは有効なメールアドレスであり、「引用符で囲まれていればスペースを含めることができます」@ foo.comと同様です。@の前の部分はすべて無視し、ドメイン部分のみを検証するのが最も簡単です。
Nerdmaster 2012

6
いくつかの誤ったアドレスを許可することについて心配するべきではないという動機に同意します。残念なことに、この正規表現は正しいアドレスを許可しません。おそらく、このようなものが良いでしょうか?/.+@.+\..+/
ZoFreX

12

Rails 3でメール検証用のgemを作成しました。Railsにはデフォルトでこのようなものが含まれていないことにちょっと驚いています。

http://github.com/balexand/email_validator


8
これは基本的に正規表現のラッパーです。
Rob Dawson、

ifor unlessステートメントでこれを使用する方法の例を挙げられますか?ドキュメントはまばらです。
cwd

@cwdドキュメントは完全だと思います。Rails 3以上の検証に慣れていない場合は、このRailscast(railscasts.com/episodes/211-validations-in-rails-3)またはguides.rubyonrails.org/active_record_validations.html
balexand


7

Railsの4つのドキュメント

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 Person < ActiveRecord::Base
  validates :email, presence: true, email: true
end

5

Rails 4では、モデルにvalidates :email, email:trueフィールドを追加すると仮定して(フィールドがと呼ばれる場合email)、単純に(または複雑な†)EmailValidatorを記述して、ニーズに合わせます。

例:-モデル:

class TestUser
  include Mongoid::Document
  field :email,     type: String
  validates :email, email: true
end

あなたのバリデーター(に行きますapp/validators/email_validator.rb

class EmailValidator < ActiveModel::EachValidator
  EMAIL_ADDRESS_QTEXT           = Regexp.new '[^\\x0d\\x22\\x5c\\x80-\\xff]', nil, 'n'
  EMAIL_ADDRESS_DTEXT           = Regexp.new '[^\\x0d\\x5b-\\x5d\\x80-\\xff]', nil, 'n'
  EMAIL_ADDRESS_ATOM            = Regexp.new '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+', nil, 'n'
  EMAIL_ADDRESS_QUOTED_PAIR     = Regexp.new '\\x5c[\\x00-\\x7f]', nil, 'n'
  EMAIL_ADDRESS_DOMAIN_LITERAL  = Regexp.new "\\x5b(?:#{EMAIL_ADDRESS_DTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x5d", nil, 'n'
  EMAIL_ADDRESS_QUOTED_STRING   = Regexp.new "\\x22(?:#{EMAIL_ADDRESS_QTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x22", nil, 'n'
  EMAIL_ADDRESS_DOMAIN_REF      = EMAIL_ADDRESS_ATOM
  EMAIL_ADDRESS_SUB_DOMAIN      = "(?:#{EMAIL_ADDRESS_DOMAIN_REF}|#{EMAIL_ADDRESS_DOMAIN_LITERAL})"
  EMAIL_ADDRESS_WORD            = "(?:#{EMAIL_ADDRESS_ATOM}|#{EMAIL_ADDRESS_QUOTED_STRING})"
  EMAIL_ADDRESS_DOMAIN          = "#{EMAIL_ADDRESS_SUB_DOMAIN}(?:\\x2e#{EMAIL_ADDRESS_SUB_DOMAIN})*"
  EMAIL_ADDRESS_LOCAL_PART      = "#{EMAIL_ADDRESS_WORD}(?:\\x2e#{EMAIL_ADDRESS_WORD})*"
  EMAIL_ADDRESS_SPEC            = "#{EMAIL_ADDRESS_LOCAL_PART}\\x40#{EMAIL_ADDRESS_DOMAIN}"
  EMAIL_ADDRESS_PATTERN         = Regexp.new "#{EMAIL_ADDRESS_SPEC}", nil, 'n'
  EMAIL_ADDRESS_EXACT_PATTERN   = Regexp.new "\\A#{EMAIL_ADDRESS_SPEC}\\z", nil, 'n'

  def validate_each(record, attribute, value)
    unless value =~ EMAIL_ADDRESS_EXACT_PATTERN
      record.errors[attribute] << (options[:message] || 'is not a valid email')
    end
  end
end

これにより、「test+no_really@test.tes」などのタグ付きメールを含む、あらゆる種類の有効なメールが許可されます。

でこれをテストrspecするにはspec/validators/email_validator_spec.rb

require 'spec_helper'

describe "EmailValidator" do
  let(:validator) { EmailValidator.new({attributes: [:email]}) }
  let(:model) { double('model') }

  before :each do
    model.stub("errors").and_return([])
    model.errors.stub('[]').and_return({})  
    model.errors[].stub('<<')
  end

  context "given an invalid email address" do
    let(:invalid_email) { 'test test tes' }
    it "is rejected as invalid" do
      model.errors[].should_receive('<<')
      validator.validate_each(model, "email", invalid_email)
    end  
  end

  context "given a simple valid address" do
    let(:valid_simple_email) { 'test@test.tes' }
    it "is accepted as valid" do
      model.errors[].should_not_receive('<<')    
      validator.validate_each(model, "email", valid_simple_email)
    end
  end

  context "given a valid tagged address" do
    let(:valid_tagged_email) { 'test+thingo@test.tes' }
    it "is accepted as valid" do
      model.errors[].should_not_receive('<<')    
      validator.validate_each(model, "email", valid_tagged_email)
    end
  end
end

これはとにかく私がやった方法です。YMMV

†正規表現は暴力のようなものです。それらが機能しない場合は、それらを十分に使用していません。


1
私はあなたの検証を使いたくなりますが、それをどこから入手したのか、どのようにしてそれを作ったのかはわかりません。教えてもらえますか?
Mauricio Moraes

私はグーグル検索から正規表現を得て、ラッパーコードとスペックテストを自分で書きました。
デイブ・サグ

1
あなたもテストを投稿したのは素晴らしいことです!しかし、本当に私を動かしたのは、そこにあるパワークオートです!:)
Mauricio Moraes 14年

4

以下のようハレルヤが示唆私が使用して考えてメールの宝石は良いアプローチです。しかし、私はそこにあるフープのいくつかが嫌いです。

私が使う:

def self.is_valid?(email) 

  parser = Mail::RFC2822Parser.new
  parser.root = :addr_spec
  result = parser.parse(email)

  # Don't allow for a TLD by itself list (sam@localhost)
  # The Grammar is: (local_part "@" domain) / local_part ... discard latter
  result && 
     result.respond_to?(:domain) && 
     result.domain.dot_atom_text.elements.size > 1
end

TLD(トップレベルドメイン)がこのリストに含まれていることを要求することで、より厳格になる可能性がありますが、新しいTLDがポップアップするたびに(2012の追加.mobiやなど.tel)、そのリストを更新する必要があります。

パーサは、直接フックの利点は、文法メールのルールメール宝石の用途は、それは、アドレスは次のように解析することができるように設計された部分のために、かなり広いですuser<user@example.com>どのSMTPのために一般的です。Mail::Addressあなたからそれを消費することにより、あなたは追加のチェックの束を行うことを余儀なくされます。

Mail gemに関するもう1つの注意事項は、クラスはRFC2822と呼ばれていますが、文法には、このテストなど、RFC5322のいくつかの要素があります。


1
このスニペットをありがとう、サム。Mail gemによって提供される一般的な「ほとんどの場合十分な」妥当性検証がないことに少し驚いています。
JD。

4

Rails 3 では、この素晴らしい投稿で説明されているように、再利用可能なバリデーターを作成することができます。

http://archives.ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators

class EmailValidator < ActiveRecord::Validator   
  def validate()
    record.errors[:email] << "is not valid" unless
    record.email =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i   
  end
end

そしてそれを使ってvalidates_with

class User < ActiveRecord::Base   
  validates_with EmailValidator
end

3

他の答えに注目しても、問題はまだ残っています-なぜそれについて賢く悩むのですか?

多くの正規表現が否定または見逃す可能性のあるエッジケースの実際の量には問題があるようです。

問題は「達成しようとしていることは何か」だと思います。メールアドレスを「検証」しても、実際に有効なメールアドレスであることを検証しているわけではありません。

正規表現を使用する場合は、クライアント側に@があるかどうかを確認してください。

不適切な電子メールシナリオについては、コードに「メッセージを送信できませんでした」ブランチを作成します。


1

基本的に3つの最も一般的なオプションがあります。

  1. 正規表現(万能な電子メールアドレスの正規表現はありません。自分で登録してください)
  2. MXクエリ(つまり、使用しているもの)
  3. アクティベーショントークンを生成してメールで送信する(restful_authentication方法)

validates_email_veracity_ofとトークン生成の両方を使用したくない場合は、古い学校の正規表現チェックを使用します。


1

Mail gemには、アドレスパーサーが組み込まれています。

begin
  Mail::Address.new(email)
  #valid
rescue Mail::Field::ParseError => e
  #invalid
end

Rails 3.1では動作しないようです。Mail :: Address.new( "john")は、例外を発生させることなく、新しいMail :: Addressオブジェクトを喜んで返します。
jasoncrawford

OK、例外がスローされることもありますが、すべてではありません。@ハレルヤのリンクは、ここで良いアプローチを持っているようです。
jasoncrawford

1

このソリューションは、@ SFEleyおよび@Alessandro DSによる回答に基づいており、リファクタリングと使用方法の明確化が行われています。

次のように、モデルでこのバリデータークラスを使用できます。

class MyModel < ActiveRecord::Base
  # ...
  validates :colum, :email => { :allow_nil => true, :message => 'O hai Mark!' }
  # ...
end

あなたがあなたのapp/validatorsフォルダに次のものを持っていると仮定します(Rails 3):

class EmailValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    return options[:allow_nil] == true if value.nil?

    unless matches?(value)
      record.errors[attribute] << (options[:message] || 'must be a valid email address')
    end
  end

  def matches?(value)
    return false unless value

    if /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value).nil?
      false
    else
      true
    end

  end
end

1

以下のためのメーリングリスト検証。(私はRails 4.1.6を使用しています)

ここから正規表現を取得しました。それは非常に完全なもののようで、多くの組み合わせに対してテストされています。そのページで結果を見ることができます。

Rubyの正規表現に少し変更して、 lib/validators/email_list_validator.rb

これがコードです:

require 'mail'

class EmailListValidator < ActiveModel::EachValidator

  # Regexp source: https://fightingforalostcause.net/content/misc/2006/compare-email-regex.php
  EMAIL_VALIDATION_REGEXP   = Regexp.new('\A(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))\z', true)

  def validate_each(record, attribute, value)
    begin
      invalid_emails = Mail::AddressList.new(value).addresses.map do |mail_address|
        # check if domain is present and if it passes validation through the regex
        (mail_address.domain.present? && mail_address.address =~ EMAIL_VALIDATION_REGEXP) ? nil : mail_address.address
      end

      invalid_emails.uniq!
      invalid_emails.compact!
      record.errors.add(attribute, :invalid_emails, :emails => invalid_emails.to_sentence) if invalid_emails.present?
    rescue Mail::Field::ParseError => e

      # Parse error on email field.
      # exception attributes are:
      #   e.element : Kind of element that was wrong (in case of invalid addres it is Mail::AddressListParser)
      #   e.value: mail adresses passed to parser (string)
      #   e.reason: Description of the problem. A message that is not very user friendly
      if e.reason.include?('Expected one of')
        record.errors.add(attribute, :invalid_email_list_characters)
      else
        record.errors.add(attribute, :invalid_emails_generic)
      end
    end
  end

end

モデルで次のように使用します。

validates :emails, :presence => true, :email_list => true

これは、次のようなメーリングリストを検証し、セパレータとシンタックスが異なります。

mail_list = 'John Doe <john@doe.com>, chuck@schuld.dea.th; David G. <david@pink.floyd.division.bell>'

この正規表現を使用する前にを使用しましたがDevise.email_regexp、これは非常に単純な正規表現であり、必要なすべてのケースを取得できませんでした。いくつかのメールがぶつかった。

私はウェブから他の正規表現を試しましたが、これはこれまでで最高の結果を得ました。それがあなたの場合に役立つことを願っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.