如何在Ruby on Rails中进行电子邮件验证?

Has*_*mad 46 ruby email ruby-on-rails

我在Rails中进行电子邮件验证:

validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
Run Code Online (Sandbox Code Playgroud)

此外,我在前端进行HTML5验证,但电子邮件地址如

..abc@gmail.com
.abc@gmail.com
Run Code Online (Sandbox Code Playgroud)

仍然有效.我错过了什么?

Jos*_*ter 132

我在标准的ruby库中使用常量内置的URI

validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } 
Run Code Online (Sandbox Code Playgroud)

  • `fakeemail @ gmailcom`是一封有效的电子邮件,请点击此处https://en.wikipedia.org/wiki/Email_address#Examples (9认同)
  • 注意!我尝试使用错误的电子邮件`!('fakeemail @ gmailcom'=〜URI :: MailTo :: EMAIL_REGEXP).nil?`它应该返回false,但返回true,这是不正确的 (5认同)
  • 此方法不检查域 (4认同)
  • 如果有一段时间,但没有任何后续,正则表达式将返回零.如果在一段时间后或没有一段时间后有什么东西,它就会通过."这个要求是故意违反RFC 5322,它定义了同时过于严格(在"@"字符之前),太模糊(在"@"字符之后)的电子邮件地址的语法,并且过于宽松(允许注释,空白字符和大多数用户不熟悉的引用字符串)在这里具有实际用途." https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address (2认同)
  • @medBouzid 因为 fakeemail@gmailcom 是有效的电子邮件格式。我接下来会检查域。 (2认同)
  • 不确定是否只有我一个人,但使用上述解决方案,我仍然收到以“.”开头的电子邮件,视为有效。 (2认同)

Nat*_*ate 20

更新:我刚刚找到了看起来非常棒的valid_email2 gem.

不要使用正则表达式进行电子邮件地址验证.这是一个陷阱.有比您想象的更有效的电子邮件地址格式.然而!该mail宝石(它是由要求的ActionMailer,让你拥有它)会解析电子邮件地址-一个合适解析器-为您提供:

require 'mail'
a = Mail::Address.new('foo@example.com')
Run Code Online (Sandbox Code Playgroud)

Mail::Field::ParseError如果这是一个不合规的电子邮件地址,这将抛出一个.(我们没有涉及到进行MX地址查找等任何事情.)

如果您想获得良好的'Rails验证器体验,您可以app/models/concerns/email_validatable.rb:

require 'mail'

module EmailValidatable
  extend ActiveSupport::Concern

  class EmailValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
      begin
        a = Mail::Address.new(value)
      rescue Mail::Field::ParseError
        record.errors[attribute] << (options[:message] || "is not an email")
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

然后在您的模型中,您可以:

include EmailValidatable
validates :email, email: true
Run Code Online (Sandbox Code Playgroud)

正如Iwo Dziechciarow在下面提到的评论所提到的,这传递了任何有效的"To:"地址.所以类似的东西Foo Bar <foo.bar@example.com>是有效的.这对你来说可能是个问题,也可能不是; 毕竟,它确实一个有效的地址.

如果你只想要它的地址部分:

a = Mail::Address.new('Foo Bar <foobar@example.com>')
a.address
=> "foobar@example.com"
Run Code Online (Sandbox Code Playgroud)

正如BjörnWeinbrenne在下面指出的那样,有更多有效的RFC2822地址比你预期的更多(我很确定那里列出的所有地址都是兼容的,并且可能根据系统配置接收邮件) - 这就是为什么我不建议尝试正则表达式,但使用兼容的解析器.

如果你真的在乎你是否可以发送电子邮件到一个地址,那么你最好的选择 - 到目前为止 - 实际上是发送带有验证链接的邮件.


Mar*_* T. 13

如果您已在应用中使用Devise gem,则可能适合使用

email =~ Devise.email_regexp
Run Code Online (Sandbox Code Playgroud)

...这也意味着应用程序的不同位置使用相同的验证.

  • 设备正则表达式非常简单:/\A [^ @\s] + @ [^ @\s] +\z /以下电子邮件地址未被检测为无效:info @ example,com,info @ example,info, foo@example.com (4认同)
  • 是的,这看起来很酷.`validates:email,format:{with:Devise.email_regexp,message:"invalid email"}` (2认同)
  • 如果你已经在使用设计,你的用户模型应该已经在这样做了 (2认同)

Cyz*_*far 10

这是进行电子邮件验证的新 Rails 方式:

validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
Run Code Online (Sandbox Code Playgroud)

请参阅Rails 验证文档

  • 滚动到底部总是一个好主意。只有一点反对它接受`foo,bar@hooli.com`。对我的情况来说不是问题。 (3认同)

ton*_*roo 7

@Nate非常感谢您将这个答案放在一起.在我查看您的代码片段之前,我没有意识到电子邮件验证有如此多的细微差别.

我注意到当前邮件gem:mail-2.6.5不会为"abc"的电子邮件抛出错误.例子:

>> a = Mail::Address.new('abc')
=> #<Mail::Address:70343701196060 Address: |abc| >
>> a.address # this is weird
=> "abc"
>> a = Mail::Address.new('"Jon Doe" <jon@doe.com>')
=> #<Mail::Address:70343691638900 Address: |Jon Doe <jon@doe.com>| >
>> a.address
=> "jon@doe.com"
>> a.display_name
=> "Jon Doe"
>> Mail::Address.new('"Jon Doe <jon')
Mail::Field::ParseError: Mail::AddressList can not parse |"Jon Doe <jon|
Reason was: Only able to parse up to "Jon Doe <jon
  from (irb):3:in `new'
  from (irb):3
>>
Run Code Online (Sandbox Code Playgroud)

它确实会引发Mail::Field::ParseError错误"Jon Doe <jon.我相信也会检查简单的"abc模式".

app/models/concerns/pretty_email_validatable.rb:

require 'mail'

module PrettyEmailValidatable
  extend ActiveSupport::Concern

  class PrettyEmailValidator < ActiveModel::EachValidator

    def validate_each(record, attribute, value)
      begin
        a = Mail::Address.new(value)
      rescue Mail::Field::ParseError
        record.errors[attribute] << (options[:message] || "is not an email")
      end

      # regexp from http://guides.rubyonrails.org/active_record_validations.html
      value = a.address
      unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
        record.errors[attribute] << (options[:message] || "is not an email")
      end
    end

  end
end
Run Code Online (Sandbox Code Playgroud)

然后在您的模型中,您可以:

include PrettyEmailValidatable
validates :pretty_email, email: true
Run Code Online (Sandbox Code Playgroud)

所以我使用上面的"漂亮的电子邮件"验证和https://github.com/balexand/email_validator进行标准的电子邮件验证.


Jac*_*rry 5

如果其他人非常注重TDD:我希望我可以针对这些东西编写测试,并在以后需要时进行改进,而无需将测试与其他模型捆绑在一起。

建立在NateTangeroo的代码(感谢NateTangeroo!)的基础上,在Rails 5,中完成Ruby 2.4.1。这是我投入的内容app/validators/email_validator.rb

require 'mail'

class EmailValidator < ActiveModel::EachValidator
  def add_error(record, attribute)
    record.errors.add(attribute, (options[:message] || "is not a valid email address"))
  end

  def validate_each(record, attribute, value)
    begin
      a = Mail::Address.new(value)
    rescue Mail::Field::ParseError
      add_error(record, attribute)
    end

    # regexp from http://guides.rubyonrails.org/active_record_validations.html
    value = a.address unless a.nil?
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      add_error(record, attribute)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

这绝不是全面的,但这是我提出的内容spec/validators/email_validator_spec.rb

require 'rails_helper'

RSpec.describe EmailValidator do
  subject do
    Class.new do
      include ActiveModel::Validations
      attr_accessor :email
      validates :email, email: true
    end.new
  end

  context 'when the email address is valid' do
    let(:email) { Faker::Internet.email }

    it 'allows the input' do
      subject.email = email
      expect(subject).to be_valid
    end
  end

  context 'when the email address is invalid' do
    let(:invalid_message) { 'is not a valid email address' }

    it 'invalidates the input' do
      subject.email = 'not_valid@'
      expect(subject).not_to be_valid
    end

    it 'alerts the consumer' do
      subject.email = 'notvalid'
      subject.valid?
      expect(subject.errors[:email]).to include(invalid_message)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!


Adr*_*dao 5

最好遵循 Rails 文档:

https://guides.rubyonrails.org/active_record_validations.html#custom-validators

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 < ApplicationRecord
  validates :email, presence: true, email: true
end
Run Code Online (Sandbox Code Playgroud)