您使用什么来验证用户的电子邮件地址,为什么?
我一直在使用validates_email_veracity_of
哪个实际查询MX服务器。但是由于种种原因,这充满了失败,主要与网络流量和可靠性有关。
我环顾四周,找不到很多人用来对电子邮件地址进行完整性检查的明显信息。是否有为此维护的,相当准确的插件或gem?
PS:请不要告诉我发送带有链接的电子邮件,以查看该电子邮件是否有效。我正在开发“发送给朋友”功能,因此这不切实际。
您使用什么来验证用户的电子邮件地址,为什么?
我一直在使用validates_email_veracity_of
哪个实际查询MX服务器。但是由于种种原因,这充满了失败,主要与网络流量和可靠性有关。
我环顾四周,找不到很多人用来对电子邮件地址进行完整性检查的明显信息。是否有为此维护的,相当准确的插件或gem?
PS:请不要告诉我发送带有链接的电子邮件,以查看该电子邮件是否有效。我正在开发“发送给朋友”功能,因此这不切实际。
Answers:
###@domain.com
会验证吗?
不要使它变得比需要的难。您的功能不重要;验证只是捕捉错别字的基本理智步骤。我会用一个简单的正则表达式来做到这一点,并且不会在任何过于复杂的事情上浪费CPU周期:
/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/
改编自http://www.regular-expressions.info/email.html-如果您确实想知道所有折衷方法,则应阅读该文章。如果您想要更正确,更复杂且完全符合RFC822的正则表达式,则也可以在该页面上找到。但问题是这样的:您不必完全正确。
如果地址通过验证,您将发送一封电子邮件。如果电子邮件失败,您将收到一条错误消息。这时您可以告诉用户“对不起,您的朋友没有收到,您想再试一次吗?” 或将其标记为手动审核,或忽略它或其他任何内容。
如果地址确实通过了验证,则这些选项与您必须处理的选项相同。因为即使您的验证是完美的并且您获得地址存在的绝对证明,发送仍然可能失败。
验证误报的成本很低。更好的验证的好处也很低。进行大量验证,并在发生错误时担心它们。
我在Rails 3中创建了一个用于电子邮件验证的gem。我很惊讶Rails默认情况下不包含这样的内容。
if
or unless
语句一起使用?文档似乎很少。
目前,该项目在github上的监视人数最多(用于Rails中的电子邮件验证):
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
在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
无论如何,这就是我所做的。青年汽车
†正则表达式就像暴力;如果它们不起作用,则说明您使用的不够。
正如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
您可以通过要求的顶级域名(顶级域名)是严格的这份名单,但你将不得不更新列表,新顶级域名弹出(如2012除了.mobi
和.tel
)
直接挂接解析器的优点是 对于Mail gem所使用的部分,Mail语法中规则相当广泛,它旨在允许其解析user<user@example.com>
SMTP常用的地址。通过从中消费它,Mail::Address
您被迫进行大量额外的检查。
在Rails 3中,可以编写可重用的验证器,如这篇出色的文章所述:
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
Mail gem具有内置的地址解析器。
begin
Mail::Address.new(email)
#valid
rescue Mail::Field::ParseError => e
#invalid
end
该解决方案基于@SFEley和@Alessandro DS的答案,并带有重构和用法说明。
您可以在模型中使用此验证器类,如下所示:
class MyModel < ActiveRecord::Base
# ...
validates :colum, :email => { :allow_nil => true, :message => 'O hai Mark!' }
# ...
end
假设您的app/validators
文件夹中包含以下内容(第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
用于邮件列表验证。(我使用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
它将使用不同的分隔符和synthax验证像这样的邮件列表:
mail_list = 'John Doe <john@doe.com>, chuck@schuld.dea.th; David G. <david@pink.floyd.division.bell>'
在使用此regexp之前,我使用过Devise.email_regexp
,但这是一个非常简单的regexp,没有得到我需要的所有情况。一些电子邮件碰到了。
我从网上尝试了其他正则表达式,但是到目前为止,这是最好的结果。希望它对您有帮助。