Валидация за гранью фола

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

    Ситуация раз


    При регистрации пользователя мы как обычно хотим сделать подтверждение пароля. Нет проблем, добавляем :confirmation => true. Через какое-то время у сайта появляется мобильное приложение, в котором тоже реализована регистрация, но подтверждения пароля там уже нет. Как поступить в этом случае?

    решение под катом


    Самый популярный ответ: в контроллере руками проставляем password_confirmation. Постойте. Какое отношение подтверждение пароля имеет к модели пользователя? Что вообще такое подтверждение пароля? А подтверждение емейла (да, некоторые делают и так)?

    Ситуация два


    Та же регистрация. Емейл как обычно обязательный. Product Owner добавляет задачу интеграции с соц. сетями. Посмотрев документацию по авторизации через твиттер, понимаем что емейл нам не видать. Кто-то, конечно, после авторизации попросит ввести емейл, но в нашем случае руководство против. Нужно авторизовывать без емейла и точка, но при этом форма регистрации должна требовать емейл в любом случае. А что делать в таком случае?

    Ответы которые я слышал:
    1. скипаем валидацию в регистрации.
    2. ставим фейковый емейл — qwerty@twitter.fake.com.
    3. путем хаков вырезаем сообщение об ошибки из errors и делаем вид что все хорошо 0_o.
    4. В зависимости от приходящих параметров внутри модели срабатывает кастомная валидация.


    Является ли email обязательным для модели юзера при таких требованиях? Ответ: Нет, наше приложение должно корректно работать и при его отсутствии.

    А как же тогда форма регистрации?

    Истина где-то рядом


    Если внимательно присмотреться к первой и второй ситуации, становится понятно, что форма это нечто большее чем html кусок (в api кстати нет html, но там тоже есть “форма”). Так вот именно форма в конкретных ситуациях и должна валидировать подтверждение пароля, наличие емейла т.к. это поведение не модели, а конкретной формы в конкретном представлении. Самое забавное, что такой проблемы нет во многих других фреймворках. Модель формы есть в django, zend framework, symfony, yii. И даже есть попытки сделать подобное в rails. Так же можно попробовать реализовать эту функциональность через ActiveModel.

    Что лично меня огорчает во всей этой истории, так это то что сами разработчики rails, показывают совершенно не тот путь для решения этих задач. Они добавляют валидаторы наподобие :confirmation => true, фактически нарушая базовый принцип mvc: модель не зависит от представления.

    Мы для своих проектов нашли решение и пока он нас в целом устраивает, а заодно решает и еще одну известную проблему. Он заключается в том, что для конкретных форм мы создаем наследников наших моделей и работаем фактически через них. Конечно же это самый натуральный хак, но в попытке написать отдельные формы, я столкнулся со сложностями при реализации nested форм и отложил это дело до лучших времен.

    1. Делаем в apps папочку types.
    2. Добавляем туда base_type.rb

    module BaseType
      extend ActiveSupport::Concern
    
      module ClassMethods
        def model_name
          superclass.model_name
        end
      end
    end
    


    3. Создаем нужный type. Пример:

    class UserEditType < User
      include BaseType
    
      attr_accessible :first_name, :second_name
    
      validates :first_name, :presence => true
      validates :second_name, :presence => true
    end
    


    Так с помощью простого наследования мы решили задачу кастомной валидации в зависимости от текущего представления и требований к нему. Правда придется еще поправить завязки некоторых гемов на имя класса, а не model_name (carrierwave, например, пути строит на основе class), но пока это разрешалось достаточно легко.

    Почему он называется Type, а не Form? В api формы как таковой нет, но требования те же. А название Type взято из symfony framework.

    Из последнего примера видно, что решается и другая проблема, связанная с attr_accessible. Вы конечно можете возразить, что для этого можно пользоваться опцией :as, но в реальности она нарушает инкапсуляцию, добавляя в модель информацию о вышележащем слое.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 17
    • +3
      На мой взгляд контроль целостности данных это все-таки задача модели. В приведенных вами примерах, речь идет все го лишь о разных типах пользователей, и конкретная валидация будет просто зависеть от типа конкретного пользователя. Если это обычный пользователь, то проверяем чтоб у него был пароль и email, если это twitter-пользователь, то проверяем чтоб у него был oauth ключи от твиттера.

      В случае мобильного приложения в котором не нужно проверять email пользователя, мы именно в реализации API передает модели что-то типа email_confirmed => true, потому что тут как бы API говорит о том что с email этого пользователя все ок и его не нужно дополнительно проверять.
      • +2
        Целостность данных != валидация формы.

        Форм может быть много, и валидация в них может быть разная.
      • +2
        Думаю, здесь путаются понятия валидации, как целостности данных, и валидации, как корректности ввода.
        И если целостность данных должна относиться к уровню модели, то корректность ввода — это уровень представления… или в крайнем случае — контроллер, поскольку view-уровень в рельсах тупой. Но по MVC он и должен быть тупым. А потому, IMHO, тут стоит говорить не о косяках модельной валидации, а о недостаточности MVC. То есть просится или «презентер» или ViewModel.
        У нас это решилось просто. Мы отказались от рельсовского View-уровня, ограничив рельсы выдачей json, а все остальное — на backbone.js При этом бекбоновские вьюшки как занимают соответствующий слой, валидируя корректность ввода.
        • 0
          А как же защита от атак? Все, что проверяется на клиенте, надо проверять и на сервере, как минимум.
          • 0
            Не все!
            Только то, что связано с целостностью данных. Проверка того, что пользователь ввел именно то, что хотел ввести (например — повтор пароля), это задача уровня представления.
            • 0
              в первую очередь на сервере… плохая тенденция концентрироваться на клиенте
          • +9
            Всегда так делал:

            # user.rb
            
            validate :email,
              presence: true,
              email: true
              unless: :skip_email_validation?
            
            validate :password,
              confirmation: true,
              unless: :skip_password_confirmation?
            
            def skip_password_confirmation?
              persisted? and !password_changed?
            end
            
            def skip_email_validation?
              twitter_account? # просто пример, но логика, думаю, ясна
            end
            


            А дальше уже рулить всем этим по вкусу. Контроль целостности — задача модели. Модель должна быть валидироваться вне зависимости от контекста. Буть то юзер-из-твиттера или обычный.
            • 0
              всегда можно сделать виртуальный аттрибут registration_type, например, а в методе skip_email_validation? проверять пропускать ли валидацию мыла или нет. В контроллере же, не забывать подставить этот самый атрибут, или даже в роутере.
            • +1
              Что то странное по ссылке yii, стандартно у него есть сценарии, которые можно указывать в зависимости от текущей ситуации, благодаря этому применяются нужные правила валидации, в соответствии с выбранным сценарием.
              • 0
                Rails поддерживает «Single table inheritance» (не знаю как можно перевести на русский). Вполне можно создать 2 модели для одной табилицы и обрабатывать подобные ситуации именно так.
              • +1
                ох ты ж
                а я почему-то думал, что это одному мне не хватает аналога symfony forms
                • 0
                  Они были жуткими, громоздкими и совершенно не очевидными. Всех возможностей форм можно добиться и без подобных аналогов.
                  • 0
                    например, реализовать пошаговый мастер. или форму ввода инфы о кредитке.
                    на самом деле лично мне из symfony forms нужна только валидация — рисовать формы в рельсах отлично получается и искаропки.
                • 0
                  Zend_Form все же с моделями не связан никак — с легкостью реализовывал описанные в статье «проблемы» валиадации без всякого напряга. Так что «not affected».
                  • +1
                    Киню сюда ссылочку на коллекцию способов реализации Form Object-ов в ruby — www.evernote.com/shard/s8/sh/40214637-b7b4-4955-a7ce-d3110102f6a6/4dc21e28153d8e49bcd83e80ebc4cb0f

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.