2009-07-20 2 views
95

Что вы используете для проверки адресов электронной почты пользователей и почему?Какова современность в проверке подлинности электронной почты для Rails?

Я использовал validates_email_veracity_of, который фактически запрашивает серверы MX. Но это поломка по разным причинам, в основном связанная с сетевым трафиком и надежностью.

Я огляделся по сторонам, и я не мог найти ничего очевидного, что многие люди используют для проверки правильности адреса электронной почты. Есть ли для этого поддерживаемый, достаточно точный плагин или драгоценный камень?

P.S .: Пожалуйста, не говорите мне, чтобы отправить электронное письмо со ссылкой, чтобы узнать, работает ли электронное письмо. Я разрабатываю функцию «Отправить другу», поэтому это не практично.

+0

Вот супер-простой способ, не имея дело с регулярным выражением: [detecting-a-valid-email-address] (http: // lindsaar.net/2008/4/14/tip-4-detecting-a-valid-email-address) – Zabba

+0

Не могли бы вы дать более подробную информацию о том, почему запрос на сервер MX терпит неудачу? Я хотел бы знать, поэтому я могу понять, можно ли их устранить. – lulalala

ответ

67

С Rails 3.0 вы можете использовать проверку электронной почты без регулярного выражения, используя Mail gem.

my implementation (packaged as a gem).

+0

Ницца, я использую ваш драгоценный камень. Благодарю. – jasoncrawford

+0

выглядит как '### @ domain.com' будет проверять? – cwd

+1

Ребята, я хотел бы возродить этот камень, у меня не было времени его поддерживать. Но, похоже, люди все еще используют его и ищут улучшения. Если вы заинтересованы, напишите мне в проекте github: hallelujah/valid_email – Hallelujah

1

Там в основном 3 наиболее распространенные варианты:

  1. Regexp (не работает для всех, адрес электронной почты регулярного выражения, так что свернуть свой собственный)
  2. MX запроса (то есть то, что вы используете)
  3. Создание маркера активации и отправить его (restful_authentication путь)

Если вы не хотите использовать как validates_email_veracity_of и маркер поколения, я бы со старой проверки школы регулярных выражений.

106

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

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

Это была заимствована из http://www.regular-expressions.info/email.html - что вы должны прочитать, если вы действительно хотите знать все компромиссы. Если вы хотите получить более правильное и намного более сложное полноправильное регулярное выражение, совместимое с RFC822, это тоже на этой странице. Но дело в следующем: вам не нужно полностью это понимать.

Если адрес проходит проверку, вы отправите электронное письмо. Если сообщение не удалось, вы получите сообщение об ошибке. В этот момент вы можете сообщить пользователю «Извините, ваш друг не получил это, вы бы хотели попробовать еще раз?» или отметьте его для ручного просмотра или просто проигнорируйте его или что-то еще.

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

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

+35

Err, не будет ли это баф на музее и новых международных TLD? Это регулярное выражение * предотвратило бы * много действительных адресов электронной почты. – Elijah

+3

Согласен с Илья, это плохая рекомендация. Кроме того, я не уверен, как вы думаете, что вы можете сказать пользователю, что его друг не получил электронное письмо, потому что нет способа сказать, удалось ли письмо с места в карьер. – Jaryl

+8

Хорошая точка в **. Музее ** и тому подобное - когда я впервые опубликовал этот ответ в 2009 году, это не было проблемой. Я изменил регулярное выражение. Если у вас есть дополнительные улучшения, вы также можете отредактировать его или сделать это сообществом wiki. – SFEley

6

Update: TMail был заменен Mail gem

Как SFEley сказал, что это зависит от того, насколько тщательно вы хотите быть.В большинстве случаев его регулярное выражение достаточно. Я просто использую библиотеку TMail Ruby для проверки любого юридического адреса электронной почты, возможно, за счет некоторых циклов процессора.

begin 
    TMail::Address.parse(email_address) 
    return true 
rescue 
    return false 
end 
+0

В качестве примечания, драгоценность TMail теперь перечеркнута почтовым камнем (тем же автором). – lulalala

12

Я создал драгоценный камень для проверки подлинности электронной почты в Rails 3. Я удивлен, что Rails не включает в себя что-то подобное по умолчанию.

http://github.com/balexand/email_validator

+8

Это по существу обертка вокруг регулярного выражения. –

+0

Можете ли вы привести пример использования этого с помощью инструкции 'if' или' except'? Документация кажется редкой. – cwd

+0

@cwd Я думаю, что документация завершена. Если вы не знакомы с проверками Rails 3+, ознакомьтесь с этим Railscast (http://railscasts.com/episodes/211-validations-in-rails-3) или http://guides.rubyonrails.org/active_record_validations .html – balexand

1

Почтовый драгоценный камень имеет встроенный адрес анализатор.

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

Кажется, не работает для меня в Rails 3.1. Mail :: Address.new («john») с радостью возвращает мне новый объект Mail :: Address, не создавая исключения. – jasoncrawford

+0

ОК, в некоторых случаях это исключает исключение, но не все. Ссылка @ Аллилуйя, кажется, имеет хороший подход здесь. – jasoncrawford

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 
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 ([email protected]) 
    # 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 достаточно широк для частей почты камень использует, он предназначен, чтобы позволить ему разобрать адрес, как user<[email protected]>, который является общим для SMTP. Поглощая его от Mail::Address, вы вынуждены выполнять кучу дополнительных чеков.

Другое примечание относительно почтового жемчужина, хотя класс называется RFC2822, грамматика содержит некоторые элементы RFC5322, например this test.

+1

Спасибо за этот фрагмент, Сэм. Я немного удивлен, что не существует общей проверки «достаточно хорошо в большинстве случаев», предоставляемой почтой gem. –

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._%+-][email protected][A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value).nil? 
     false 
    else 
     true 
    end 

    end 
end 
3

отмечая другие ответы, то вопрос все еще остается - зачем беспокоиться об этом?

Фактический объем реберных кейсов, которые многие регулярные выражения могут отрицать или пропустить, кажется проблематичным.

Я думаю, что вопрос: «Что я пытаюсь добиться?», Даже если вы «проверяете» адрес электронной почты, вы на самом деле не проверяете, что это рабочий адрес электронной почты.

Если вы хотите использовать регулярное выражение, просто проверьте наличие @ на стороне клиента.

Что касается неверного сценария электронной почты, у вас есть сообщение «не удалось отправить» ветке.

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

В 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 

Это позволит все виды допустимых сообщений электронной почты, в том числе помечено электронной почты, как «тест + 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) { '[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 

Это, как я сделал это в любом случае. YMMV

† Регулярные выражения напоминают насилие; если они не работают, вы недостаточно используете их.

+1

У меня возникает соблазн использовать вашу проверку, но я понятия не имею, откуда вы ее взяли или как вы ее создали. Вы можете рассказать нам? –

+0

Я получил регулярное выражение из поиска Google, и сам написал код обертки и тесты. –

+1

Отлично, что вы отправили тесты, а также! Но то, что действительно получило меня, было силовым предложением там! :) –

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 

Он будет проверять списки рассылки, подобные этому, с различными разделителями и синтаксисом:

mail_list = 'John Doe <[email protected]>, [email protected]; David G. <[email protected]>' 

Перед использованием этого регулярного выражения я использовал Devise.email_regexp, но это очень простое регулярное выражение и не получило всех необходимых мне случаев. Некоторые электронные письма столкнулись.

Я пробовал другие регулярные выражения из Интернета, но до сих пор у этого есть лучшие результаты. Надеюсь, это поможет в вашем случае.

Смежные вопросы