Qu'utilisez-vous pour valider les adresses e-mail des utilisateurs et pourquoi?
J'utilisais validates_email_veracity_of
qui interroge réellement les serveurs MX. Mais cela est plein d'échecs pour diverses raisons, principalement liées au trafic réseau et à la fiabilité.
J'ai regardé autour de moi et je n'ai rien trouvé d'évident que beaucoup de gens utilisent pour effectuer une vérification de santé mentale sur une adresse e-mail. Existe-t-il un plugin ou un bijou maintenu et raisonnablement précis?
P.S .: Veuillez ne pas me dire d'envoyer un e-mail avec un lien pour voir si l'e-mail fonctionne. Je développe une fonctionnalité "envoyer à un ami", donc ce n'est pas pratique.
Avec Rails 3.0, vous pouvez utiliser une validation par e-mail sans regex en utilisant Mail gem .
Voici mon implémentation ( emballé comme un joya ).
Ne rendez pas cela plus difficile que nécessaire. Votre fonctionnalité n'est pas critique; la validation n'est qu'une étape de base pour détecter les fautes de frappe. Je le ferais avec une regex simple, et ne gaspillerais pas les cycles CPU sur quelque chose de trop compliqué:
/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/
Cela a été adapté de http://www.regular-expressions.info/email.html - que vous devriez lire si vous voulez vraiment connaître tous les compromis. Si vous voulez une expression régulière plus correcte et beaucoup plus compliquée entièrement conforme à RFC822, c'est également sur cette page. Mais la chose est la suivante: vous n'avez pas besoin de bien faire les choses.
Si l'adresse passe la validation, vous allez envoyer un e-mail. Si l'e-mail échoue, vous allez recevoir un message d'erreur. À quel moment vous pouvez dire à l'utilisateur "Désolé, votre ami n'a pas reçu cela, voulez-vous réessayer?" ou le signaler pour manuel revoir, ou tout simplement l'ignorer, ou autre chose.
Ce sont les mêmes options que vous auriez à traiter si l'adresse avait passé la validation. Car même si votre validation est parfaite et que vous acquérez une preuve absolue que l'adresse existe, l'envoi peut toujours échouer.
Le coût d'un faux positif à la validation est faible. L'avantage d'une meilleure validation est également faible. Validez généreusement et craignez les erreurs lorsqu'elles se produisent.
J'ai créé un joyau pour la validation des e-mails dans Rails 3. Je suis un peu surpris que Rails n'inclut pas quelque chose comme ça par défaut).
Ce projet semble avoir le plus de téléspectateurs sur github en ce moment (pour la validation des e-mails dans Rails):
De la 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
Dans Rails 4 ajoutez simplement validates :email, email:true
(en supposant que votre champ s'appelle email
) dans votre modèle, puis écrivez un simple (ou complexe †) EmailValidator
pour répondre à vos besoins.
ex: - votre modèle:
class TestUser
include Mongoid::Document
field :email, type: String
validates :email, email: true
end
Votre validateur (va dans 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
Cela permettra toutes sortes de courriels valides, y compris marqués courriels comme "[email protected]" et ainsi de suite.
Pour tester cela avec rspec
dans votre 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
C'est comme ça que je l'ai fait de toute façon. YMMV
† Les expressions régulières sont comme la violence; s'ils ne fonctionnent pas, vous n'en utilisez pas suffisamment.
Dans Rails 3, il est possible d'écrire un validateur réutilisable , comme l'explique cet excellent article:
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
et l'utiliser avec validates_with
:
class User < ActiveRecord::Base
validates_with EmailValidator
end
Comme Alléluia le suggère, je pense que l'utilisation du Mail gem est une bonne approche. Cependant, je n'aime pas certains des cerceaux là-bas.
J'utilise:
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
Vous pourriez être plus strict en exigeant que les TLD (domaines de premier niveau) soient dans cette liste , mais vous seriez obligé de mettre à jour cette liste à mesure que de nouveaux TLD apparaîtront (comme l'ajout de 2012 .mobi
et .tel
)
L'avantage de raccorder directement l'analyseur est que les règles de la grammaire du courrier sont assez larges pour les parties utilisées par le gem Mail, il est conçu pour lui permettre d'analyser une adresse comme user<[email protected]>
ce qui est courant pour SMTP. En le consommant à partir du Mail::Address
vous êtes obligé de faire un tas de vérifications supplémentaires.
Une autre note concernant la gemme Mail, même si la classe s'appelle RFC2822, la grammaire a certains éléments de RFC5322 , par exemple ce test .
Compte tenu des autres réponses, la question demeure - pourquoi se soucier d'être intelligent à ce sujet?
Le volume réel de cas Edge que de nombreuses expressions régulières peuvent nier ou manquer semble problématique.
Je pense que la question est "qu'est-ce que j'essaie de réaliser?", Même si vous "validez" l'adresse e-mail, vous ne validez pas réellement qu'il s'agit d'une adresse e-mail fonctionnelle.
Si vous optez pour l'expression régulière, vérifiez simplement la présence de @ du côté client.
En ce qui concerne le scénario de courrier électronique incorrect, une branche "message n'a pas pu être envoyé" à votre code.
La gemme Mail a un analyseur d'adresses intégré.
begin
Mail::Address.new(email)
#valid
rescue Mail::Field::ParseError => e
#invalid
end
Cette solution est basée sur les réponses de @SFEley et @Alessandro DS, avec un refactor, et une clarification de l'utilisation.
Vous pouvez utiliser cette classe de validateur dans votre modèle comme ceci:
class MyModel < ActiveRecord::Base
# ...
validates :colum, :email => { :allow_nil => true, :message => 'O hai Mark!' }
# ...
end
Étant donné que vous disposez des éléments suivants dans votre app/validators
dossier (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
Pour Validation des listes de diffusion . (J'utilise Rails 4.1.6)
J'ai obtenu mon expression rationnelle de ici . Il semble que ce soit très complet, et il a été testé contre un grand nombre de combinaisons. Vous pouvez voir les résultats sur cette page.
Je l'ai légèrement changé en Ruby regexp, et l'ai mis dans mon lib/validators/email_list_validator.rb
Voici le 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
Et je l'utilise comme ça dans le modèle:
validates :emails, :presence => true, :email_list => true
Il validera les listes de diffusion comme celle-ci, avec différents séparateurs et synthax:
mail_list = 'John Doe <[email protected]>, [email protected]; David G. <[email protected]>'
Avant d'utiliser cette expression rationnelle, j'ai utilisé Devise.email_regexp
, mais c'est une expression rationnelle très simple et je n'ai pas obtenu tous les cas dont j'avais besoin. Certains courriels se sont heurtés.
J'ai essayé d'autres expressions rationnelles sur le Web, mais celle-ci a obtenu les meilleurs résultats jusqu'à présent. J'espère que cela vous aidera dans votre cas.
Il existe essentiellement 3 options les plus courantes:
Si vous ne souhaitez pas utiliser à la fois validates_email_veracity_of et la génération de jetons, je choisirais la vérification des expressions rationnelles à l'ancienne.