- PVSM.RU - https://www.pvsm.ru -

Ruby: про email вообще и ради валидации без регекспов, в частности

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

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

Автор: Renius

Источник [1]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ruby/44019

Ссылки в тексте:

[1] Источник: http://habrahabr.ru/post/175399/