web-dev-qa-db-ja.com

Railsの電子メール検証の最新技術は何ですか?

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

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

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

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

95
Luke Francl

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

私の実装gemとしてパッケージ化 )です。

67
Hallelujah

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

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

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

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

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

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

106
SFEley

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

http://github.com/balexand/email_validator

12
balexand

このプロジェクトは、現時点では最も多くのウォッチャーがgithubにいるようです(Railsでの電子メール検証用)。

https://github.com/alexdunae/validates_email_format_of

10
Brian Armstrong

Rails 4 docs から:

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
7
Mikey

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

これにより、tagged "[email protected]"などの電子メールなど、あらゆる種類の有効な電子メールが許可されます。

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

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) { '[email protected]' }
    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) { '[email protected]' }
    it "is accepted as valid" do
      model.errors[].should_not_receive('<<')    
      validator.validate_each(model, "email", valid_tagged_email)
    end
  end
end

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

†正規表現は暴力に似ています。動作しない場合は、十分に使用していません。

5
Dave Sag

Rails 3では、reusableバリデーターを書くことができます。

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
4
Alessandro DS

Hallelujah が示唆するように、 Mail gem を使用することは良いアプローチだと思います。ただし、そこにあるフープのいくつかは嫌いです。

私が使う:

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

パーサーを直接フックする利点は、Mail gemが使用する部分に対して Mail grammarのルール がかなり広いことです。これは、user<[email protected]>などのアドレスを解析できるように設計されています。 SMTPに共通。 Mail::Addressからそれを消費することで、余分なチェックをたくさん行わなければなりません。

Mail gemに関する別の注意点は、クラスがRFC2822と呼ばれていても、文法には RFC5322 の要素がいくつかあります。たとえば this test です。

4
Sam Saffron

他の答えに注目すると、問題はまだ残っています-なぜそれについて気になるのですか?

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

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

正規表現を使用する場合は、クライアント側で@の存在を確認してください。

誤った電子メールのシナリオについては、「メッセージの送信に失敗しました」ブランチをコードに追加してください。

3
muttonlamb

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

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

このソリューションは、@ 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
thekingoftruth

メーリングリスト検証 (Rails 4.1.6)を使用します)

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

私はそれをRuby regexpに少し変更し、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 <[email protected]>, [email protected]; David G. <[email protected]>'

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

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

1
Mauricio Moraes

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

  1. 正規表現(すべての場合に使用できる電子メールアドレス正規表現はありませんので、独自に展開してください)
  2. MXクエリ(使用するもの)
  3. アクティベーショントークンを生成して郵送する(restful_authentication方法)

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

1
Yaroslav