Вместо предисловия
В первой статье я расскажу о проблеме, которая встала передо мной при работе над rails-админкой для проекта, написанного на php/PostgreSQL.
Проект был в общем-то реализован до меня, и когда я пришел в команду, у него было 3 интерфейса: для пользователей, для менеджеров, и для админов. Причем, если первые два работали в любых браузерах и были выполнены практически в одном и том же дизайне, то админка работала исключительно в IE, и вроде бы использовала какой-то ActiveX (или что-то подобное, совместимое исключительно с виндой).
Я, работая на макбуке, разумеется, не имел ни малейшей возможности пользоваться этим достижением прогресса. Точнее, поначалу я ходил на офисный виндовый сервер по VNC, и там запускал горячо любимый браузер, но это был ад, и вскоре я понял, что больше так нельзя, и сел писать свою админку.
Среда
Проект разрабатывался на отдельном development-сервере, затем выкладывался на своеобразный staging-сервер, где его прорабатывал тестировщик, и смотрели (и снова тестировали) люди из компании-заказчика проекта; и уже после окончательного утверждения все переносилось на production.
У каждой среды был свой отдельный postgres-сервер, и периодически возникала необходимость смотреть и править данные на development и staging серваках. Структура БД, разумеется, была идентичной.
Взявшись за админку, я начал с описания стандартных MVC-компонентов, запуская все это добро локально в dev-режиме, нацеленным на development-базу. Когда я окончательно убедился в том, что админка не порушит ничего важного в БД, я решился запустить ее на staging - опять же, локально, при этом я вписал настройки соединения в рельсовую среду development и поочередно раскомментировал то один набор настроек, то другой. Выглядело это следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
С подобным механизмом проект прожил пару месяцев, но постоянно приходилось мучиться с раскомментированием нужных строк и закомментированием ненужных, и разумеется последующим рестартом сервера. Вскоре мне это надоело, и я начал продумывать более простой способ переключения между соединениями.
ActiveRecord::Base.establish_connection(:dev)
Именно этот метод отвечает в AR за установку соединения с БД. Обычно рельсы вызывают его автоматически, передавая параметром название среды, которое представлено в config/database.yml
.
Моя задача состояла в том, чтобы AR вызывал этот метод самостоятельно, и в качестве параметра передавал название нужного соединения, которое можно было бы переключать из контроллеров. В результате получился следующий initializer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
К сожалению, для поддержки работы с выбранным соединением не придумалось ничего лучше, чем вызов choose_db
при определении модели, при этом для корректной работы ассоциаций они должны браться из одной и той же базы, поэтому choose_db
должен вызываться в обеих моделях.
1 2 3 4 |
|
При этом если в системе подключена авторизация через какую-то модель (в моем случае к модели Admin
был пристегнут Devise), то в этой модели работа должна идти всегда с одной и той же БД - т.е. в этой модели вызывать choose_db
нельзя.
Соединения пришлось разбить на две группы - для рельсовой среды development и production, в каждой среде должны были работать все соединения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Далее нужно было просто вызывать ActiveRecord::Base.set_connection
при каждом запросе, и ActiveRecord::Base.restore_connection
после отработки запроса (последний нужен был для того, чтобы отрабатывать запрос с запрошенной базой, но не менять выбранное соединение в session[:db]
- чтобы при дальнейших запросах работать уже с соединением из сессии). Это вылилось в before_filter
и after_filter
в ApplicationController
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Дальше дело оставалось за малым - был необходим контроллер, переключающий в сессии выбранное соединение, и ссылки на единственный экшен этого контроллера с именем соединения в качестве параметра:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Ну и дальше просто подвешиваем ссылки в лэйауте, где-нибудь в области шапки:
1 2 3 4 |
|
Вместо заключения
Конечно, решение получилось немного костыльным в некоторых местах, но оно позволило переключаться между БД из интерфейса самого приложения, а также появилась возможность давать ссылку на страницу, которая будет отрендерена с использованием переданного соединения, не меняя при этом session[:db]
, например http://dev.domain.tld/users/123?db=staging
.
Возможно, когда-нибудь у меня дойдут руки вычистить весь этот код, написать спеки и оформить в виде gem-а, к тому же есть пока еще нерешенные задачи из смежных областей - например, работа одновременно с несколькими схемами в postgres (имеются в виду конечно схемы самого постгреса, а не рельсовая schema.rb
). Но на данный момент при необходимости это решение вполне можно использовать.