Ruby-on-rails Rails 电子邮件验证的最新技术是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1156601/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What's the state of the art in email validation for Rails?
提问by Luke Francl
What are you using to validate users' email addresses, and why?
您使用什么来验证用户的电子邮件地址,为什么?
I had been using validates_email_veracity_ofwhich actually queries the MX servers. But that is full of fail for various reasons, mostly related to network traffic and reliability.
我一直在使用validates_email_veracity_of它实际上查询 MX 服务器。但由于各种原因,这充满了失败,主要与网络流量和可靠性有关。
I looked around and I couldn't find anything obvious that a lot of people are using to perform a sanity check on an email address. Is there a maintained, reasonably accurate plugin or gem for this?
我环顾四周,我找不到很多人用来对电子邮件地址执行健全性检查的明显内容。是否有一个维护的,相当准确的插件或 gem?
P.S.: Please don't tell me to send an email with a link to see if the email works. I'm developing a "send to a friend" feature, so this isn't practical.
PS:请不要告诉我发送带有链接的电子邮件以查看电子邮件是否有效。我正在开发“发送给朋友”功能,所以这不切实际。
采纳答案by Hallelujah
With Rails 3.0 you can use a email validation without regexp using the Mail gem.
使用 Rails 3.0,您可以使用Mail gem使用电子邮件验证,而无需使用正则表达式。
Here is my implementation(packaged as a gem).
回答by SFEley
Don't make this harder than it needs to be. Your feature is non-critical; validation's just a basic sanity step to catch typos. I would do it with a simple regex, and not waste the CPU cycles on anything too complicated:
不要让它变得比需要的更难。您的功能不重要;验证只是捕捉错别字的基本理智步骤。我会用一个简单的正则表达式来做,而不是在任何太复杂的事情上浪费 CPU 周期:
/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/
That was adapted from http://www.regular-expressions.info/email.html-- which you should read if you really want to know all the tradeoffs. If you want a more correct and much more complicated fully RFC822-compliant regex, that's on that page too. But the thing is this: you don't have to get it totally right.
这是改编自http://www.regular-expressions.info/email.html—— 如果你真的想知道所有的权衡,你应该阅读它。如果你想要一个更正确、更复杂的完全符合 RFC822 的正则表达式,那也在那个页面上。但问题是:你不必完全正确。
If the address passes validation, you're going to send an email. If the email fails, you're going to get an error message. At which point you can tell the user "Sorry, your friend didn't receive that, would you like to try again?"or flag it for manual review, or just ignore it, or whatever.
如果地址通过验证,您将发送一封电子邮件。如果电子邮件失败,您将收到一条错误消息。此时您可以告诉用户“抱歉,您的朋友没有收到,您想再试一次吗?” 或将其标记为人工审核,或忽略它,或其他什么。
These are the same options you'd have to deal with if the address didpass validation. Because even if your validation is perfect and you acquire absolute proof that the address exists, sending could still fail.
如果地址确实通过了验证,这些选项与您必须处理的选项相同。因为即使您的验证是完美的并且您获得了地址存在的绝对证据,发送仍然可能失败。
The cost of a false positive on validation is low. The benefit of better validation is also low. Validate generously, and worry about errors when they happen.
验证误报的成本很低。更好的验证带来的好处也很低。慷慨地验证,并在错误发生时担心。
回答by balexand
I created a gem for email validation in Rails 3. I'm kinda surprised that Rails doesn't include something like this by default.
我在 Rails 3 中创建了一个用于电子邮件验证的 gem。我有点惊讶 Rails 默认不包含这样的东西。
回答by Brian Armstrong
This project seems to have the most watchers on github at the moment (for email validation in rails):
目前这个项目似乎在 github 上拥有最多的观察者(用于 Rails 中的电子邮件验证):
回答by Mikey
From the Rails 4 docs:
来自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
回答by Dave Sag
In Rails 4 simply add validates :email, email:true(assuming your field is called email) to your model and then write a simple (or complex?) EmailValidatorto suit your needs.
在 Rails 4 中,只需将validates :email, email:true(假设您的字段称为email)添加到您的模型中,然后编写一个简单的(或复杂的?)EmailValidator来满足您的需求。
eg: - your model:
例如: - 你的模型:
class TestUser
include Mongoid::Document
field :email, type: String
validates :email, email: true
end
Your validator (goes in app/validators/email_validator.rb)
你的验证器(进去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
This will allow all sorts of valid emails, including taggedemails like "[email protected]" and so on.
这将允许各种有效的电子邮件,包括标记的电子邮件,如“[email protected]”等。
To test this with rspecin your spec/validators/email_validator_spec.rb
要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) { '[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
This is how I've done it anyway. YMMV
反正我就是这样做的。青年会
?Regular expressions are like violence; if they don't work you are not using enough of them.
?正则表达式就像暴力;如果它们不起作用,您就没有使用足够的它们。
回答by Alessandro DS
In Rails 3 it's possible to write a reusablevalidator, as this great post explains:
在 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
and use it with validates_with:
并将其用于validates_with:
class User < ActiveRecord::Base
validates_with EmailValidator
end
回答by Sam Saffron
As Hallelujahsuggests I think using the Mail gemis a good approach. However, I dislike some of the hoops there.
正如哈利路亚所建议的那样,我认为使用Mail gem是一个好方法。但是,我不喜欢那里的一些篮球。
I use:
我用:
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
You could be stricter by demanding that the TLDs (top level domains) are in this list, however you would be forced to update that list as new TLDs pop up (like the 2012 addition .mobiand .tel)
您可以更严格地要求 TLD(顶级域)在此列表中,但是当新 TLD 弹出时,您将被迫更新该列表(如 2012 年的添加.mobi和.tel)
The advantage of hooking the parser direct is that the rules in Mail grammarare fairly wide for the portions the Mail gem uses, it is designed to allow it to parse an address like user<[email protected]>which is common for SMTP. By consuming it from the Mail::Addressyou are forced to do a bunch of extra checks.
直接挂钩解析器的优点是Mail 语法中的规则对于 Mail gem 使用的部分相当广泛,它旨在允许它解析类似于user<[email protected]>SMTP 常见的地址。通过从 消费它,Mail::Address你被迫做一堆额外的检查。
Another note regarding the Mail gem, even though the class is called RFC2822, the grammar has some elements of RFC5322, for example this test.
关于 Mail gem 的另一个注意事项,即使该类称为 RFC2822,语法也包含RFC5322 的一些元素,例如这个 test。
回答by muttonlamb
Noting the other answers, the question still remains - why bother being clever about it?
注意到其他答案,问题仍然存在 - 为什么要聪明?
The actual volume of edge cases that many regex may deny or miss seems problematic.
许多正则表达式可能否认或遗漏的边缘情况的实际数量似乎有问题。
I think the question is 'what am I trying to acheive?', even if you 'validate' the email address, you're not actually validating that it is a working email address.
我认为问题是“我想要达到什么目的?”,即使您“验证”了电子邮件地址,您实际上并没有验证它是一个有效的电子邮件地址。
If you go for regexp, just check for the presence of @ on the client side.
如果您使用正则表达式,只需检查客户端是否存在 @。
As for the incorrect email scenario, have a 'message failed to send' branch to your code.
至于不正确的电子邮件场景,请在您的代码中添加一个“消息发送失败”分支。
回答by Mauricio Moraes
For Mailing Lists Validation. (I use Rails 4.1.6)
用于邮件列表验证。(我使用 Rails 4.1.6)
I got my regexp from here. It seems to be a very complete one, and it's been tested against a great number of combinations. You can see the results on that page.
我从这里得到了我的正则表达式。它似乎是一个非常完整的组合,并且已经针对大量组合进行了测试。您可以在该页面上查看结果。
I slightly changed it to a Ruby regexp, and put it in my lib/validators/email_list_validator.rb
我稍微将其更改为 Ruby 正则表达式,并将其放入我的 lib/validators/email_list_validator.rb
Here's the 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
And I use it like this in the model:
我在模型中像这样使用它:
validates :emails, :presence => true, :email_list => true
It will validate mailing lists like this one, with different separators and synthax:
它将验证像这样的邮件列表,使用不同的分隔符和 synthax:
mail_list = 'John Doe <[email protected]>, [email protected]; David G. <[email protected]>'
Before using this regexp, I used Devise.email_regexp, but that is a very simple regexp and didn't get all the cases I needed. Some emails bumped.
在使用这个正则表达式之前,我使用了Devise.email_regexp,但这是一个非常简单的正则表达式,并没有得到我需要的所有情况。一些电子邮件被撞了。
I tried other regexps from the web, but this one's got the best results till now. Hope it helps in your case.
我尝试了网络上的其他正则表达式,但到目前为止,这个结果是最好的。希望它对您的情况有所帮助。

