Как выполнить проверку подлинности в Ruby on Rails?

Я выполняю проверку электронной почты в Rails с помощью:

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

Кроме того, я проверяю HTML5 во внешнем интерфейсе, но адреса электронной почты, такие как

[email protected]
[email protected]

все еще действительны. Что мне не хватает?

Ответ 1

Я использую постоянную, встроенную в URI в стандартной библиотеке ruby

validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } 

Ответ 2

Обновление: я только что нашел камень valid_email2, который выглядит очень хорошо.

Не используйте регулярное выражение для проверки адреса электронной почты. Это ловушка. Есть гораздо больше допустимых форматов адресов электронной почты, чем вы думаете. Тем не мение! Gem mail (он требуется ActionMailer, поэтому он у вас есть) проанализирует адреса электронной почты - с правильным анализатором - для вас:

require 'mail'
a = Mail::Address.new('[email protected]')

Это выдаст 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

и затем в своей модели вы можете:

include EmailValidatable
validates :email, email: true

Как упоминает ниже комментарий Иво Дзеччарова, он пропускает все, через что проходит действительный адрес "Кому:". Так что что-то вроде Foo Bar <[email protected]> действительно. Это может быть проблемой для вас, это не может быть; В конце концов, это действительно правильный адрес.

Если вам нужна только адресная часть:

a = Mail::Address.new('Foo Bar <[email protected]>')
a.address
=> "[email protected]"

Как отмечает Бьорн Вайнбренне ниже, есть более допустимые адреса RFC2822, чем вы можете ожидать (я вполне уверен, что все перечисленные адреса соответствуют требованиям и могут получать почту в зависимости от конфигурации системы) - Вот почему я не рекомендую пробовать регулярные выражения, но использую совместимый парсер.

Если вам действительно важно, можете ли вы отправить электронное письмо на адрес, тогда лучше всего - на самом деле - отправить сообщение со ссылкой для подтверждения.

Ответ 3

Если вы используете драгоценный камень Devise уже в своем приложении, возможно, будет удобно использовать

email =~ Devise.email_regexp

... что также означает, что разные места приложения используют ту же проверку.

Ответ 4

@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" <[email protected]>')
=> #<Mail::Address:70343691638900 Address: |Jon Doe <[email protected]>| >
>> a.address
=> "[email protected]"
>> 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
>>

Это выдает ошибки 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

и затем в своей модели вы можете:

include PrettyEmailValidatable
validates :pretty_email, email: true

Таким образом, я использую вышеупомянутое для проверки "симпатичной электронной почты" и https://github.com/balexand/email_validator для стандартной проверки электронной почты.

Ответ 6

Если кто-то еще очень сфокусирован на TDD: мне нужно что-то, что я мог бы написать тесты против и улучшить позже, если это необходимо, без привязки тестов к другой модели.

Создание Nate и tongueroo кода (спасибо Nate и tongueroo!), это было сделано в 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

И это ни в коем случае не является всеобъемлющим, но вот что я бросил в 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 = '[email protected]'
      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

Надеюсь, что это поможет!

Ответ 7

Ниже приведен новый способ проверки электронной почты:

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

Обратитесь к Rails valications doc.

Ответ 8

Простой ответ: Не используйте регулярное выражение. Слишком много краевых случаев и ложных негативов и ложных срабатываний. Проверьте знак @и отправьте письмо на адрес, чтобы проверить его:

https://www.youtube.com/watch?v=xxX81WmXjPg

Ответ 10

Вы можете попробовать это

VALID_EMAIL_REGEX = /\A[\w+\-.][email protected][a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i

Ответ 11

попробуйте это.

validates_format_of  :email, :with => /^[\+A-Z0-9\._%-][email protected]([A-Z0-9-]+\.)+[A-Z]{2,4}$/i

Ответ 12

Лучше всего следовать документации 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