Каково современное состояние проверки электронной почты для Rails?
что вы используете для проверки адресов электронной почты пользователей, а почему?
я использовал validates_email_veracity_of
, который фактически запрашивает MX-сервера. Но это полно сбоев по разным причинам, в основном связанных с сетевым трафиком и надежностью.
Я посмотрел вокруг, и я не мог найти ничего очевидного, что многие люди используют для выполнения проверки здравомыслия на адрес электронной почты. Есть ли поддерживаемый, достаточно точный плагин или драгоценный камень для это?
P. S.: И пожалуйста, не говори мне отправить электронное письмо со ссылкой, чтобы увидеть, если почта работает. Я разрабатываю функцию "Отправить другу", так что это не практично.
13 ответов:
с Rails 3.0 вы можете использовать проверку электронной почты без регулярного выражения с помощью Mail gem.
Не делайте это сложнее, чем это должно быть. Ваша функция не критична; проверка-это всего лишь базовый шаг здравомыслия, чтобы поймать опечатки. Я бы сделал это с помощью простого регулярного выражения, а не тратить циклы процессора на что-то слишком сложное:
/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/
Это было адаптировано из http://www.regular-expressions.info/email.html -- который вы должны прочитать, если вы действительно хотите знать все компромиссы. Если вы хотите более правильный и гораздо более сложный полностью RFC822-совместимый регулярное выражение, это тоже на этой странице. Но дело вот в чем:вы не должны получить его совершенно правы.
Если адрес проходит проверку, вы собираетесь отправить электронное письмо. Если сообщение электронной почты не удается, вы получите сообщение об ошибке. В этот момент Вы можете сказать пользователю "Извините, ваш друг не получил это, вы хотели бы попробовать еще раз?" или пометить его для ручного просмотра, или просто игнорировать его, или что-то еще.
Это те же самые параметры, которые у вас были бы если адрес сделал проверки. Потому что даже если ваша проверка совершенна, и вы получаете абсолютное доказательство того, что адрес существует, отправка все равно может потерпеть неудачу.
стоимость ложного срабатывания при проверке низкая. Преимущество лучшей валидации также невелико. Проверить щедро, и беспокоиться об ошибках, когда они происходят.
Я создал драгоценный камень для проверки электронной почты в Rails 3. Я немного удивлен, что Rails не включает что-то вроде этого по умолчанию.
этот проект, похоже, имеет большинство наблюдателей на github на данный момент (для проверки электронной почты в rails):
С 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
в Rails 4 Просто добавьте
validates :email, email:true
(предполагая, что поле называется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.тес " и так далее.
чтобы проверить это с
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
†регулярные выражения подобны насилию; если они не работают, вы не используете их в достаточном количестве.
как Аллилуйя предлагает мне думать, используя 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
вы могли бы быть строже, требуя, чтобы дву (домены верхнего уровня) находились в этот список, однако вы будете вынуждены обновить этот список по мере появления новых дву (например, добавление 2012 года
.mobi
и.tel
)преимущество прямого подключения парсера заключается в том, что элемент правила в грамматике Почты довольно широки для частей, которые использует Mail gem, он разработан, чтобы позволить ему анализировать адрес, такой как
user<user@example.com>
что является общим для SMTP. Потребляя его изMail::Address
вы вынуждены делать кучу дополнительных проверок.еще одно замечание относительно почтового драгоценного камня, хотя класс называется RFC2822, грамматика имеет некоторые элементы RFC5322, например этот тест.
в 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
и использовать его с
validates_with
:class User < ActiveRecord::Base validates_with EmailValidator end
отмечая другие ответы,Вопрос все еще остается-зачем беспокоиться об этом?
фактический объем краевых случаев, которые многие регулярные выражения могут отрицать или пропустить, кажется проблематичным.
Я думаю, что вопрос в том, что я пытаюсь достичь?", даже если вы "проверяете" адрес электронной почты, вы на самом деле не проверяете, что это рабочий адрес электронной почты.
Если вы идете на регулярное выражение, просто проверьте наличие @ на стороне клиента.
как для в неправильном сценарии электронной почты есть ветвь "сообщение не удалось отправить" в ваш код.
есть в основном 3 наиболее распространенных варианта:
- обработать (нет работает для всех адресов электронной почты с помощью регулярных выражений, чтобы свернуть свой собственный)
- MX запрос (это то, что вы используете)
- создание токена активации и отправка его по почте (restful_authentication way)
Если вы не хотите использовать как validates_email_veracity_of, так и генерацию токенов, я бы пошел с проверкой регулярных выражений старой школы.
почтовый камень имеет встроенный анализатор адресов.
begin Mail::Address.new(email) #valid rescue Mail::Field::ParseError => e #invalid end
Это решение основано на ответах @SFEley и @Alessandro DS, с рефактором и разъяснением использования.
вы можете использовать этот класс валидатора в своей модели следующим образом:
class MyModel < ActiveRecord::Base # ... validates :colum, :email => { :allow_nil => true, :message => 'O hai Mark!' } # ... end
учитывая, что у вас есть следующее в вашем
app/validators
папка (рельсы 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
на Проверка Списков Рассылки. (Я использую рельсы 4.1.6)
я получил свое регулярное выражение от здесь. Он кажется очень полным, и он был протестирован против большого количества комбинаций. Вы можете увидеть результаты на этой странице.
Я немного изменил его на рубиновое регулярное выражение и поместил его в мой
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
, но это очень простое регулярное выражение и не получил все дела, которые мне нужны. Некоторые электронные письма наткнулись.Я пробовал другие регулярные выражения из интернета, но этот получил лучшие результаты до сих пор. Надеюсь, это поможет в вашем случае.