wake-up-neo.com

Wie ist der Stand der E-Mail-Validierung für Rails?

Womit validieren Sie die E-Mail-Adressen der Benutzer und warum?

Ich hatte validates_email_veracity_of der die MX-Server tatsächlich abfragt. Dies ist jedoch aus verschiedenen Gründen mit vielen Fehlern behaftet, hauptsächlich im Zusammenhang mit dem Netzwerkverkehr und der Zuverlässigkeit.

Ich habe mich umgesehen und konnte nichts Offensichtliches finden, das viele Leute benutzen, um eine Überprüfung der Gesundheit einer E-Mail-Adresse durchzuführen. Gibt es dafür ein gepflegtes, einigermaßen genaues Plugin oder Juwel?

P .: Bitte sagen Sie mir nicht, dass ich eine E-Mail mit einem Link senden soll, um zu sehen, ob die E-Mail funktioniert. Ich entwickle eine Funktion zum Senden an einen Freund, daher ist dies nicht praktikabel.

95
Luke Francl

Mit Rails 3.0 können Sie eine E-Mail-Validierung ohne Regex verwenden, indem Sie Mail gem verwenden.

Hier ist meine Implementierung ( als Juwel verpackt ).

67
Hallelujah

Machen Sie es sich nicht schwerer, als es sein muss. Ihre Funktion ist nicht kritisch. Die Validierung ist nur ein grundlegender Schritt, um Tippfehler zu erkennen. Ich würde es mit einer einfachen Regex machen und die CPU-Zyklen nicht mit etwas zu Kompliziertem verschwenden:

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

Das wurde angepasst von http://www.regular-expressions.info/email.html - was Sie lesen sollten, wenn Sie wirklich alle Kompromisse kennen wollen. Wenn Sie einen korrekteren und komplizierteren, vollständig RFC822-kompatiblen regulären Ausdruck wünschen, finden Sie dies auch auf dieser Seite. Aber die Sache ist die folgende: Sie müssen es nicht ganz richtig machen.

Wenn die Adresse die Validierung besteht, senden Sie eine E-Mail. Wenn die E-Mail fehlschlägt, erhalten Sie eine Fehlermeldung. An welchem ​​Punkt können Sie dem Benutzer mitteilen "Entschuldigung, Ihr Freund hat das nicht erhalten, möchten Sie es erneut versuchen?" oder es für manuell markieren überprüfen oder einfach ignorieren, oder was auch immer.

Dies sind die gleichen Optionen, mit denen Sie umgehen müssten, wenn die Adresse die Validierung bestanden hätte . Denn selbst wenn Ihre Validierung perfekt ist und Sie den absoluten Beweis dafür erhalten, dass die Adresse existiert, kann das Senden immer noch fehlschlagen.

Die Kosten für ein falsches Positiv bei der Validierung sind gering. Der Nutzen einer besseren Validierung ist ebenfalls gering. Validieren Sie großzügig und sorgen Sie sich um Fehler, wenn diese auftreten.

106
SFEley

Ich habe ein Juwel für die E-Mail-Validierung in Rails 3. Ich bin ein bisschen überrascht, dass Rails standardmäßig so etwas nicht enthält.

http://github.com/balexand/email_validator

12
balexand

Dieses Projekt scheint momentan die meisten Beobachter auf Github zu haben (für die E-Mail-Validierung in Rails):

https://github.com/alexdunae/validates_email_format_of

10
Brian Armstrong

Aus dem 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

Fügen Sie in Rails 4 einfach validates :email, email:true (Vorausgesetzt, Ihr Feld heißt email) zu Ihrem Modell hinzu und schreiben Sie dann ein einfaches (oder komplexes †) EmailValidator nach Ihren Wünschen.

zB: - Ihr Modell:

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

Ihr Validator (geht in 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

Dies erlaubt alle Arten von gültigen E-Mails, einschließlich getaggt E-Mails wie "[email protected]" und so weiter.

Testen Sie dies mit rspec in Ihrem 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) { '[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

So habe ich es trotzdem gemacht. YMMV

† Reguläre Ausdrücke sind wie Gewalt. Wenn sie nicht funktionieren, verwenden Sie nicht genug davon.

5
Dave Sag

In Rails 3 ist es möglich, einen wiederverwendbaren Validator zu schreiben, wie dieser großartige Beitrag erklärt:

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

und benutze es mit validates_with:

class User < ActiveRecord::Base   
  validates_with EmailValidator
end
4
Alessandro DS

Als Hallelujah empfinde ich die Verwendung des Mail Gem als einen guten Ansatz. Allerdings mag ich einige der Reifen dort nicht.

Ich benutze:

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 ([email protected])
  # The Grammar is: (local_part "@" domain) / local_part ... discard latter
  result && 
     result.respond_to?(:domain) && 
     result.domain.dot_atom_text.elements.size > 1
end

Sie könnten strenger sein, indem Sie verlangen, dass die TLDs (Top Level Domains) in diese Liste sind. Sie müssten diese Liste jedoch aktualisieren, wenn neue TLDs auftauchen (wie der 2012-Zusatz .mobi und .tel)

Der Vorteil des direkten Hooks des Parsers ist, dass die Regeln in der Mail-Grammatik für die Teile, die der Mail-Edelstein verwendet, ziemlich breit sind und es ihm ermöglichen, eine Adresse wie user<[email protected]> Zu analysieren ist für SMTP üblich. Wenn Sie es aus dem Mail::Address Verbrauchen, müssen Sie eine Reihe zusätzlicher Prüfungen durchführen.

Ein weiterer Hinweis zum Mail-Gem: Obwohl die Klasse RFC2822 heißt, enthält die Grammatik einige Elemente von RFC5322 , z. B. dieser Test .

4
Sam Saffron

Bei den anderen Antworten bleibt immer noch die Frage - warum sollte man sich darum kümmern, schlau zu sein?

Das tatsächliche Volumen der Edge-Fälle, die von vielen regulären Ausdrücken geleugnet oder übersehen werden, scheint problematisch zu sein.

Ich denke, die Frage lautet: "Was versuche ich zu erreichen?". Selbst wenn Sie die E-Mail-Adresse "validieren", bestätigen Sie nicht, dass es sich um eine funktionierende E-Mail-Adresse handelt.

Wenn Sie sich für reguläre Ausdrücke entscheiden, prüfen Sie einfach, ob auf der Clientseite ein @ vorhanden ist.

Haben Sie für das falsche E-Mail-Szenario einen Zweig "Nachricht konnte nicht gesendet werden" an Ihren Code.

3
muttonlamb

Das Mail-Juwel hat einen eingebauten Adress-Parser.

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

Diese Lösung basiert auf Antworten von @SFEley und @Alessandro DS mit einem Refactor und einer Erläuterung der Verwendung.

Sie können diese Validator-Klasse in Ihrem Modell folgendermaßen verwenden:

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

Vorausgesetzt, Sie haben Folgendes in Ihrem app/validators Ordner (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._%+-][email protected][A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value).nil?
      false
    else
      true
    end

  end
end
1
thekingoftruth

Für die Validierung von Mailinglisten . (Ich benutze Rails 4.1.6)

Ich habe meinen regulären Ausdruck von hier . Es scheint sehr vollständig zu sein, und es wurde gegen eine große Anzahl von Kombinationen getestet. Sie können die Ergebnisse auf dieser Seite sehen.

Ich habe es leicht in ein Ruby regexp geändert und es in mein lib/validators/email_list_validator.rb

Hier ist der Code:

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

Und ich benutze es so im Modell:

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

Es überprüft Mailinglisten wie diese mit unterschiedlichen Trennzeichen und Synthax:

mail_list = 'John Doe <[email protected]>, [email protected]; David G. <[email protected]>'

Vor der Verwendung dieses regulären Ausdrucks habe ich Devise.email_regexp, aber das ist ein sehr einfacher regulärer Ausdruck und hat nicht alle Fälle erhalten, die ich brauchte. Einige E-Mails sind gestoßen.

Ich habe andere reguläre Ausdrücke aus dem Internet ausprobiert, aber dieser hat bis jetzt die besten Ergebnisse erzielt. Hoffe es hilft in deinem Fall.

1
Mauricio Moraes

Grundsätzlich gibt es drei gängige Optionen:

  1. Regexp (es gibt keine reguläre E-Mail-Adresse, also schreiben Sie Ihre eigene)
  2. MX-Abfrage (das ist, was Sie verwenden)
  3. Aktivierungs-Token generieren und verschicken (restful_authentication way)

Wenn Sie nicht sowohl validates_email_veracity_of als auch token generation verwenden möchten, würde ich die reguläre Ausdrucksprüfung der alten Schule verwenden.

1
Yaroslav