image

Привет Хабр!

Немного наблюдений.



По предложенному вопросу пергамента исписано непозволительно много. Тем не менее, я бы хотел остановится на трех важных, но игнорируемых аттрибутах, свойственных email-у, с точки web-разработки.
Во первых email уникален, в отличии от никнейма, который, в половине случаев, занят кем-то до нас. Однако все еще встречаются сайты с логином по никнейму, который, для всех таких сайтов, ну никак не упомнить. Предлагаю использовать для логина только email.
Во вторых, часть разработчиков игнорирует type='email', когда JS валидаторы натравлены на это поле, и планшетные устройства переключают раскладку, что удобно.
В третьих, ради чего это статья, каждый год пишутся статьи вида «Почему плохо валидировать регекспом», что больше похоже на фетиш. Надеюсь гугл проиндексирует верно.

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



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

Первый показатель валидности



gem 'mail' древняя как мир рубишная библиотека
require 'mail'
mail = Mail::Addres.new('antiqe@gmail.com')
mail.local #antiqe
mail.domain #gmail.com

Это первое что нужно сделать
Если в адресе почты содержатся недопустимые символы, то библиотека вызовет exception, который нужно поймать. Однако, нам ничего не ясно про домен, и домен тут может быть инвалидным.

Второй показатель валидности



mail = Mail::Address.new('antiqe@gmail.com')
 => #<Mail::Address:72490440 Address: |antiqe@gmail.com| > 

tree = mail.__send__(:tree)
 => SyntaxNode+Address1+AddrSpec0 offset=0, "antiqe@gmail.com" (dig_comments,comments,local_part,domain):
  SyntaxNode+LocalDotAtom0 offset=0, "antiqe" (local_dot_atom_text):
    SyntaxNode+CFWS1 offset=0, "":
      SyntaxNode offset=0, ""
      SyntaxNode offset=0, ""
    SyntaxNode offset=0, "antiqe":
      SyntaxNode+LocalDotAtomText0 offset=0, "antiqe" (domain_text):
        SyntaxNode offset=0, ""
        SyntaxNode offset=0, "antiqe":
          SyntaxNode offset=0, "a"
          SyntaxNode offset=1, "n"
          SyntaxNode offset=2, "t"
          SyntaxNode offset=3, "i"
          SyntaxNode offset=4, "q"
          SyntaxNode offset=5, "e"
    SyntaxNode+CFWS1 offset=6, "":
      SyntaxNode offset=6, ""
      SyntaxNode offset=6, ""
  SyntaxNode offset=6, "@"
  SyntaxNode+DotAtom0 offset=7, "gmail.com" (dot_atom_text):
    SyntaxNode+CFWS1 offset=7, "":
      SyntaxNode offset=7, ""
      SyntaxNode offset=7, ""
    SyntaxNode offset=7, "gmail.com":
      SyntaxNode+DotAtomText0 offset=7, "gmail." (domain_text):
        SyntaxNode offset=7, "gmail":
          SyntaxNode offset=7, "g"
          SyntaxNode offset=8, "m"
          SyntaxNode offset=9, "a"
          SyntaxNode offset=10, "i"
          SyntaxNode offset=11, "l"
        SyntaxNode offset=12, "."
      SyntaxNode+DotAtomText0 offset=13, "com" (domain_text):
        SyntaxNode offset=13, "com":
          SyntaxNode offset=13, "c"
          SyntaxNode offset=14, "o"
          SyntaxNode offset=15, "m"
        SyntaxNode offset=16, ""
    SyntaxNode+CFWS1 offset=16, "":
      SyntaxNode offset=16, ""
      SyntaxNode offset=16, "" 


У нас тут синтаксическое дерево, природа и свойства которого, для меня, чуть менее чем полностью непостижимы. Знаю только то, что синтаксическое дерево, в отличии от регекспа не рекурсивно по своей природе.
Можно только предположить, что создатели библиотеки, что-то знали, но сказали не всем.
Это дерево дает нам возможность ответить на один важный вопрос: из скольких элементов состоит домен. И если таких элементов более одного — домен валиден.

Частная реализация



В рельсах достаточно вот это:

require 'mail'
class EmailValidator < ActiveModel::EachValidator
  def validate_each(record,attribute,value)
    begin
      address = Mail::Address.new(value)
      result = address.domain && address.address == value # правда, что то что распарсил Mail в сумме вернет нам обратно наш email
      tree = address.__send__(:tree)
      result &&= (tree.domain.dot_atom_text.elements.size > 1) # правда ли и то, что елементов в домене больше одного
    rescue Exception => e # ловим  исключение, если в адресе русские или китайские символы
      result = false
    end
    record.errors[attribute] << (options[:message] || "is invalid") unless result
  end
end


положить в app/validators/email_validator.rb, чтобы в любой модели использовать:

validates :email, :presence => true, :email => true


Ложноинвалидных или ложноположительных адресов за более чем два года не выявлено.
Больше сказать нечего.
Всем добра.