Develop
Управляем редиректами в одном месте
В последнее время я все больше и больше склоняюсь к тому, что хочется представлять код некоторыми абстракциями и сущностями, которые будут отвечать за логику работы системы, как отделы на предприятии или заводе, так вот, о подбной истории и захотелось написать.
Я думаю многие сталкивались с проблемой, когда при активном развитии проекта, он обрастает всевозможными пристройками, отвечающие за различные бизнес задачи. Через некоторое время, смотришь на это все со стороны и в глаза начинают бросаться дублирующиеся детали, которые вроде на своем месте, но что-то их много.
Для меня в этот раз стал наш App::BaseController
.
Вступление
Что есть App::BaseController
в разрабатываем приложение? Тут все просто, базовый контроллер для всех контроллеров пользовательской части. Еще недавно, был весьма компактный класс, всего 53 строки.
Окей, что не так?
Началось как всегда, клиент попросил реализовать подветверждение создаваемого пользователя после регистрации, классический набор, форма, проверочный код и все дела. Подтвержденные пользователи могут работать с системой, а те кто ее не прошел или того хуже был заблокирован, соотвественно не могут. Вроде все просто, нужно контролировать редирект и отправлять пользователя на нужные нам формы.
Открываем контроллер и видим
Хм, 2 проверки которые выполняют уже похожую логику, но по более простой схеме. И как обычно бывает - ладно, еще один фильтр с редиректом картину не изменит, ведь класс то и так небольшой, что плохого в еще в 1 методе на несколько строк кода? Окей, реализовываем логику, добавляем новый before_action
все работает, тесты работают, клиент доволен, вроде все хорошо. Но осадочек остался...
Хьюстон, у нас копипаст
Спустя не очен продолжительный период, возникает необходимость, добавить еще одну проверку, на этот раз это работа 2-х факторной аутентификации с своим чемоданом различных настроек и проверок. И вот тут уже глаз начинает резать по полной программе. Опять писать новый метод с редиректом по условию? Нет, так дело не пойдет.
И так, что-то нужно с этим делать. В голову как обычно бежит идея - "а давай вынесем это все в сервисный класс, который будет сам решать, когда и куда отправить", хорошо, мысль здравая, но одновременно с этим возникает вопрос, а как вписать элегантно класс в систему, чтобы как подобает ruby
коду, он читался как обычный человеческий язык, а не простой топорный redirect
.
Не знаю почему, но у меня сразу в голове возникла картина - проводник, который выбирает как будет лучше провести группу туристов через лес или какое-то иное место. И так, решено, создадим своего проводника, музыкой и печеньками.
Ближе к делу
И так, первым делом сформируем фильтр для контроллера, опишем входные данные, чтобы можно было начать писать тесты
request
по нему мы будем определять, нужно ли нам выполнять данную проверку, к примеру, исключать AJAX запросы, view_context
нужен нам для работы с методами контроллера.
Теперь определим структуру нашего сервисного класса, она будет выглядять примерно так
Следующий шаг, мы должны определиться как будем проверять необхомость в редиректе, логично, что данный класс не должен знать правил перехода, а лишь перебирать варинты и возвращать результат. Для этого определим константу ROUTES, в ней мы определим список классов, отвечающие за отдельный вид редиректа, так же нам нужно реализовать сам метод, работающий с этим списком, наша цель, найти первый положительный результат.
Из этого кода видно, что класс для проверки роута должен реализовывать 2 метода, available?
и info
, первый описывает все необходимые проверки, а второй возвращаемые результаты - ссылку для перехода и сообщение.
Окей, базовая логика написана, что теперь? Теперь нам нужно корректировать работу этого класса, просто так мы не можем отключить before_action :inspect_route
т.к. он должн работать для каждого контроллера и отключаться только если доходит до шага, который выполняет редирект на самого себя. В текущей реализации у нас произойдет зацикливание, чтобы исправить это добавим метод в контроллере который указывает на какой проверке мы должны остановится в нашем классе проводнике.
В контроллере добавляем private
метод
А в нашем классе модифицируем метод destination
следующим образом
И так, мы получили полностью работающий класс, способный управлять любым количеством редиректов в нашем приложении, скрыв всю логику в одном классе. Теперь можно соединить все описанные методы в итоговый класс, он будет выглядеть таким образом:
При необходимости его можно модифицировать под дополнительные условия.
На этом все.