Обычно про валидацию в рельсах говорят только хорошее. Сегодня мы поговорим о некоторых ситуациях где система дает сбой.
Ситуация раз
При регистрации пользователя мы как обычно хотим сделать подтверждение пароля. Нет проблем, добавляем :confirmation => true. Через какое-то время у сайта появляется мобильное приложение, в котором тоже реализована регистрация, но подтверждения пароля там уже нет. Как поступить в этом случае?
решение под катом
Самый популярный ответ: в контроллере руками проставляем password_confirmation. Постойте. Какое отношение подтверждение пароля имеет к модели пользователя? Что вообще такое подтверждение пароля? А подтверждение емейла (да некоторые делают и так)?
Ситуация два
Та же регистрация. Емейл как обычно обязательный. Product Owner добавляет задачу интеграции с соц. сетями. Посмотрев документацию по авторизации через твиттер, понимаем что емейл нам не видать. Кто-то, конечно, после авторизации попросит ввести емейл, но в нашем случае руководство против. Нужно авторизовывать без емейла и точка, но при этом форма регистрации должна требовать емейл в любом случае. А что делать в таком случае?
Ответы которые я слышал:
- скипаем валидацию в регистрации.
- ставим фейковый емейл — qwerty@twitter.fake.com.
- путем хаков вырезаем сообщение об ошибки из errors и делаем вид что все хорошо 0_o.
- В зависимости от приходящих параметров внутри модели срабатывает кастомная валидация.
Является ли 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, но в реальности она нарушает инкапсуляцию, добавляя в модель информацию о вышележащем слое.
Автор: toxicmt