Rails电子邮件验证的最新技术是什么?

时间:2009-07-20 23:53:58

标签: ruby-on-rails validation email

您使用什么来验证用户的电子邮件地址?为什么?

我一直在使用validates_email_veracity_of实际查询MX服务器。但由于各种原因,这种情况充满了失败,主要与网络流量和可靠性有关。

我环顾四周,找不到很多人用来对电子邮件地址进行健全检查的明显事实。是否有一个维护的,相当准确的插件或宝石?

P.S。:请不要告诉我发送包含链接的电子邮件,以查看电子邮件是否有效。我正在开发一个“发送给朋友”的功能,所以这不实用。

13 个答案:

答案 0 :(得分:106)

不要让它变得比它需要的更难。您的功能非关键;验证只是一个基本的理智步骤来捕捉拼写错误。我会用一个简单的正则表达式来做,而不是浪费CPU循环任何太复杂的东西:

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

这是改编自http://www.regular-expressions.info/email.html - 如果你真的想知道所有的权衡,你应该阅读。如果你想要一个更正确,更复杂的完全符合RFC822的正则表达式,那也就是在那个页面上。但问题是:你不必完全正确。

如果地址通过验证,您将发送电子邮件。如果电子邮件失败,您将收到错误消息。此时你可以告诉用户“抱歉,你的朋友没有收到,你想再试一次吗?”或标记它进行人工审核,或者只是忽略它,或者其他什么。< / p>

如果地址 通过验证,则这些选项与您必须处理的选项相同。因为即使您的验证是完美的并且您获得了地址存在的绝对证据,发送仍然可能失败。

验证的误报成本很低。更好的验证的好处也很低。慷慨地验证,并在发生错误时担心错误。

答案 1 :(得分:67)

使用Rails 3.0,您可以使用Mail gem无法使用正则表达式进行电子邮件验证。

以下是my implementationpackaged as a gem)。

答案 2 :(得分:12)

我在Rails 3中为电子邮件验证创建了一个gem。我很惊讶Rails默认不包含这样的内容。

http://github.com/balexand/email_validator

答案 3 :(得分:10)

此项目目前在github上似乎拥有最多的观察者(对于rails中的电子邮件验证):

https://github.com/alexdunae/validates_email_format_of

答案 4 :(得分:7)

来自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

答案 5 :(得分:5)

在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

无论如何我就是这样做的。 YMMV

†正则表达就像暴力;如果它们不起作用,你就没有使用它们。

答案 6 :(得分:4)

在Rails 3中,可以编写一个可重用的验证器,正如这篇伟大的帖子所解释的那样:

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

答案 7 :(得分:4)

正如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

要求顶级域名(顶级域名)位于this list,您可能会更加严格,但是当新的顶级域名弹出时,您将被迫更新该列表(例如2012年添加.mobi.tel

直接挂钩解析器的优点是rules in Mail grammar对于Mail gem使用的部分来说相当宽,它旨在允许它解析像user<user@example.com>这样常见的SMTP地址。通过从Mail::Address消费它,你被迫做了一堆额外的检查。

关于Mail gem的另一个注意事项,即使该类名为RFC2822,语法也有RFC5322的一些元素,例如this test

答案 8 :(得分:3)

注意到其他答案,这个问题仍然存在 - 为什么要为此聪明起来?

许多正则表达式可能否认或遗漏的边缘案例的实际数量似乎存在问题。

我认为问题是“我想要实现的目标是什么?”,即使您“验证”了电子邮件地址,您实际上并未验证它是否是可用的电子邮件地址。

如果你去正则表达式,只需检查客户端是否存在@。

对于错误的电子邮件方案,请将“消息无法发送”分支添加到您的代码中。

答案 9 :(得分:1)

基本上有3种最常见的选择:

  1. Regexp(没有适用于所有电子邮件地址的regexp,所以请自行填写)
  2. MX查询(即您使用的)
  3. 生成激活令牌并将其邮寄(restful_authentication way)
  4. 如果您不想同时使用validates_email_veracity_of和令牌生成,我会选择旧学校的正则表达式检查。

答案 10 :(得分:1)

Mail gem有一个内置的地址解析器。

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

答案 11 :(得分:1)

此解决方案基于@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

答案 12 :(得分:1)

邮件列表验证。 (我使用Rails 4.1.6)

我从here得到了我的正则表达式。它似乎是一个非常完整的,并且已经针对大量组合进行了测试。您可以在该页面上看到结果。

我稍微将其改为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>'

在使用此正则表达式之前,我使用了Devise.email_regexp,但这是一个非常简单的正则表达式,并没有得到我需要的所有情况。一些电子邮件碰到了。

我尝试了网络上的其他正则表达式,但到目前为止,这个结果都得到了最好的结果。希望它对你的情况有所帮助。