Який рівень техніки перевіряє електронну пошту для Rails?


95

Що ви використовуєте для перевірки електронних адрес користувачів і чому?

Я використовував, validates_email_veracity_ofякі насправді запитують сервери MX. Але це повне збоїв з різних причин, здебільшого пов'язаних з мережевим трафіком та надійністю.

Я озирнувся і не зміг знайти нічого очевидного, що багато людей використовують для перевірки стану здоров'я на адресу електронної пошти. Чи існує для цього підтримуваний, досить точний плагін чи самоцвіт?

PS: Будь ласка, не кажіть мені відправляти електронне повідомлення з посиланням, щоб перевірити, чи працює електронне повідомлення. Я розробляю функцію "надіслати другові", тому це не практично.


Ось надзвичайно простий спосіб, не маючи справи з регулярним виразом: виявлення дійсної адреси електронної пошти
Забба

Не могли б ви дати більш детальну причину, чому запит сервера MX не вдався? Я хотів би знати, щоб я міг перевірити, чи можна це виправити.
lulalala

Відповіді:


67

У Rails 3.0 ви можете використовувати перевірку електронної пошти без регулярних виразів, використовуючи поштовий камінь Mail .

Ось моя реалізація ( упакована як самоцвіт ).


Приємно, я використовую твій дорогоцінний камінь. Дякую.
jasoncrawford

схоже ###@domain.comбуде перевіряти?
cwd

1
Хлопці, я хотів би відродити цю перлину, я не встиг її зберегти. Але, здається, люди все ще використовують його і шукають вдосконалення. Якщо ви зацікавлені, напишіть мені про проект github: Алілуя / valid_email
Алілуя

106

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

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

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

Якщо адреса пройде перевірку, ви надішлете електронне повідомлення. Якщо повідомлення електронної пошти не вдається, ви отримаєте повідомлення про помилку. На цьому етапі ви можете сказати користувачеві "Вибачте, ваш друг цього не отримав, чи хочете ви спробувати ще раз?" або позначте його для ручного перегляду, або просто ігноруйте, або що завгодно.

Це ті ж самі опції , які ви хочете мати справу з тим, якщо адреса дійсно проходить перевірку. Тому що навіть якщо ваша перевірка досконала і ви отримаєте абсолютний доказ існування адреси, надсилання все одно може не вдатися.

Вартість помилково позитивного результату перевірки низька. Перевага від кращої перевірки також низька. Щедро перевіряйте і турбуйтеся про помилки, коли вони трапляються.


36
Помиляйте, чи не буде це барф про .museum та нові міжнародні TLD? Цей регулярний вираз запобігає багатьом дійсним адресам електронної пошти.
Ілля

3
Погоджений з Ілією, це погана рекомендація. Крім того, я не впевнений, як ви вважаєте, що можете сказати користувачеві, що його друг не отримав повідомлення електронної пошти, тому що неможливо визначити, чи електронний лист успішно вдався.
Ярил,

8
Хороший момент щодо .museum та подібного - коли я вперше розмістив цю відповідь у 2009 році, це не було проблемою. Я змінив регулярний вираз. Якщо у вас є подальші вдосконалення, ви також можете їх редагувати або зробити це повідомлення спільноти у вікі.
SFEley

5
FYI, при цьому все одно будуть відсутні деякі дійсні адреси електронної пошти. Не багато, але небагато. Наприклад, технічно #|@foo.com є дійсною електронною адресою, як і "Гей, у мене можуть бути пробіли, якщо вони вказані" @ foo.com. Я вважаю, що найпростіше просто ігнорувати що-небудь перед символом @ і перевіряти лише частину домену.
Nerdmaster

6
Я погоджуюсь з мотивацією, що вам не слід турбуватися про те, щоб пропустити деякі неправильні адреси. На жаль, цей регулярний вираз заборонить деякі правильні адреси, що я вважаю неприйнятними. Можливо, щось подібне було б краще? /.+@.+\..+/
ZoFreX

12

Я створив дорогоцінний камінь для перевірки електронної пошти в Rails 3. Я напевно здивований, що Rails за замовчуванням не включає щось подібне.

http://github.com/balexand/email_validator


8
По суті, це обгортка навколо регулярного виразу.
Роб Доусон,

Чи можете ви навести приклад того, як використовувати це з оператором ifor або unless? Документація здається мізерною.
cwd

@cwd Я думаю, що документація повна. Якщо ви не знайомі з перевірками Rails 3+, перевірте цей Railscast ( railscasts.com/episodes/211-validations-in-rails-3 ) або guides.rubyonrails.org/active_record_validations.html
balexand


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

Це дозволить отримувати всі види дійсних електронних листів, включаючи електронні листи з тегами, такі як "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

† Регулярні висловлювання подібні до насильства; якщо вони не працюють, ви використовуєте недостатньо їх.


1
У мене є спокуса використати вашу перевірку, але я не маю уявлення, звідки ви її взяли і як ви її зробили. Чи можете ви сказати нам?
Mauricio Moraes

Я отримав регулярний вираз із пошуку в Google, і сам написав код обгортки та специфікаційні тести.
Dave Sag

1
Чудово, що ви також розмістили тести! Але що насправді мене спонукало, це цитата влади там! :)
Mauricio Moraes

4

Як припускає Алілуя, я вважаю, що використання дорогоцінного каменя Mail - хороший підхід. Однак мені не подобаються деякі обручі там.

Я використовую:

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

Ви можете бути жорсткішими, вимагаючи, щоб TLD (домени верхнього рівня) були в цьому списку , однак ви були б змушені оновити цей список у міру появи нових TLD (наприклад, доповнення 2012 .mobiта .tel)

Перевага прямого підключення синтаксичного аналізатора полягає в тому, що правила в граматиці пошти досить широкі для частин, які використовує самоцвіт Mail, він призначений для аналізу адреси, подібної user<user@example.com>до загальної для SMTP. Споживаючи його від Mail::Addressвас, ви змушені робити купу додаткових перевірок.

Ще одна примітка щодо самоцвіту Mail, хоча клас називається RFC2822, граматика містить деякі елементи RFC5322 , наприклад цей тест .


1
Дякую за цей фрагмент, Сем. Я трохи здивований, що не є загальним підтвердженням "достатньо хорошим більшу частину часу", яке надає Mail gem.
JD.

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

3

Відзначаючи інші відповіді, все ще залишається питання - навіщо турбуватися про це розумно?

Фактичний обсяг крайових випадків, який багато регулярних виразів може заперечити або пропустити, здається проблематичним.

Я думаю, що питання полягає в тому, "що я намагаюся отримати?", Навіть якщо ви "перевіряєте" електронну адресу, ви насправді не підтверджуєте, що це робоча електронна адреса.

Якщо ви використовуєте регулярний вираз, просто перевірте наявність @ на стороні клієнта.

Що стосується неправильного сценарію електронної пошти, у вашому коді є гілка "повідомлення не вдалося надіслати".


1

В основному є 3 найпоширеніші варіанти:

  1. Regexp (регулярний вираз для електронної пошти, що працює для всіх, не існує, тому прокрутіть свій власний)
  2. Запит MX (саме цим ви користуєтесь)
  3. Створення маркера активації та відправлення його поштою (спосіб restful_authentication)

Якщо ви не хочете використовувати як validates_email_veracity_of, так і генерацію токенів, я б скористався перевіркою регулярних виразів старої школи.


1

У самоцвіті Mail вбудований синтаксичний аналізатор адрес.

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

Здається, це не працює для мене в Rails 3.1. Mail :: Address.new ("john") із задоволенням повертає мені новий об'єкт Mail :: Address, не викликаючи винятків.
jasoncrawford

Добре, у деяких випадках це спричинить виняток, але не всі. Здається, посилання @ Алілуя тут має хороший підхід.
jasoncrawford

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

1

Для перевірки списків розсилки . (Я використовую Rails 4.1.6)

Я отримав свій регулярний вираз звідси . Здається, він дуже повний, і його перевіряли на великій кількості комбінацій. Результати ви можете побачити на цій сторінці.

Я трохи змінив його на регулярний вираз 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 <john@doe.com>, chuck@schuld.dea.th; David G. <david@pink.floyd.division.bell>'

До того, як використовувати цей регулярний вираз, я використовував Devise.email_regexp, але це дуже простий регулярний вираз, і я не отримав усіх необхідних випадків. Деякі електронні листи зіткнулись.

Я спробував інші регулярні вирази з Інтернету, але цей отримав найкращі результати дотепер. Сподіваюся, це допоможе у вашому випадку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.