Генерация (скаффолдинг) админки в Ruby on Rails

Как правило каждое приложение на Ruby on Rails имеет какой-то свой административный интерфейс, если только это не совсем простенький полу-статичный сайт визитка (наверное в таком случае больше подойдут Sinatra или Padrino?). Известный человек в мире Rails — Райан Бейтс в одном из своих первых рейлскастов предлагает объединять в коде административный и публичный раздел. Думаю имеет место и другой подход. Вот почему:

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

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

Хочу рассказать вам о том, как же максимально просто провести такое разделение на примере тестового приложения.

Итак. создаем рельсовое приложение:

rails new sample

Можно также сразу прописать версию руби, если вы используете rvm

cd sample && rvm --create --ruby-version use ruby-2.0.0-p247

Открываем Gemfile и сразу же удаляем запись о геме turbolinks и запускаем bundle install --without production

Удалим //= require turbolinks из app/assets/javascripts/application.js
Да и вообще немного поменяем структуру ассетов:

app
+-/assets
  +-/javascripts
    +-/admin - здесь у нас будут отдельно храниться клиентские скрипты для разделов админки
    +-/application - здесь у нас будут отдельно храниться клиентские скрипты для публичной части сайта
    +-admin.js
    +-application.js
  +-/stylesheets
    +-/admin - здесь у нас будут отдельно храниться стили для разделов админки
    +-/application - здесь у нас будут отдельно храниться стили для страниц публичной части сайта
    +-admin.css.scss
    +-application.css.scss

И ниже приведено содержание основных файлов:

# app/assets/javascripts/admin.js
//= require jquery
//= require jquery_ujs
//= require_tree ./admin
# app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require_tree ./application
# app/assets/stylesheets/admin.css.scss
/*
 *= require_self
 *= require_tree ./admin
 */
# app/assets/stylesheets/application.css.scss
/*
 *= require_self
 *= require_tree ./application
 */

Чтобы все это дело у нас заработало в production, пропишем опции прекомпиляции ассетов:

# config/environments/production.rb
  config.assets.precompile += %w(admin.js admin.css)

Теперь создадим лейаут для нашей админки:

# app/views/layouts/admin.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Sample</title>
  <%= stylesheet_link_tag    "admin", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "admin", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

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

Теперь дело за контроллерами, вьюхами и моделями и тут начинается все самое интересное... Что мы хотим получить?

  • Контроллеры должны лежать в папке app/controllers/admin, а их классы иметь пространство имен Admin::.
  • Хелперы должны лежать в папке app/helpers/admin, а их классы — также иметь пространство имен Admin::.
  • В routes.rb путь к ресурсам должен быть в пространстве имен :admin.
  • Вьюхи должны лежать в подпапке отдельной папки app/views/admin.
  • Модель не должна иметь пространства имен и везде использоваться без него.
  • Редиректы в контроллерах должны перенаправлять на страницу в разделе admin, например, redirect_to [:admin, @post].
  • Форма создания/редактирования модели должна отправляться в админский контроллер, то есть нужно использовать form_for [:admin, @post].
  • Все ссылки должны вести на административные разделы, то есть нужно использовать [:admin, @post], admin_posts_path, edit_admin_post_path(@post) и new_admin_post_path во вьюхах и контроллерах.
  • Тесты должны учитывать все эти особенности и также, где необходимо, использовать пространство имен Admin и правильные адреса ссылок.

Стандартные генераторы Ruby on Rails сами не умеют создавать то, что нам нужно — либо модель будет с указанием пространства имен, либо пути будут не те, либо лежать все будет не там, где нужно. Мне известна лишь одна статья, которая более или менее неплохо описывает процедуру генерации такой админки, но делается это вручную. Со временем я придумал свой более простой способ генерации «правильной» структуры админки и собственно в этой статье хотел им поделиться. Однако, в ходе написания данного поста мне пришла мысль сделать свои генераторы для админки, а позднее — и решение вынести их в отдельный гем.

Так родился гем rails-admin-scaffold. Пока что он не идеален и нацелен только на Rails 4, но уже сейчас, надеюсь, кому-то может быть полезным.
Собственно все вышеперечисленные действия, которые приходилось выполнять вручную, данный гем автоматизирует. Причем на данный момент поддерживается следующее:

  • только Rails 4;
  • генерируются test::unit тесты;
  • ассеты создаются в подпапках admin;
  • имеется поддержка jbuilder;
  • имеется поддержка haml.

Планируется реализовать поддержку rspec, minitest и, возможно, formtastic/simple_form.

Использовать просто:

  1. Добавляем строку gem 'rails-admin-scaffold', 'x.x.x' в Gemfile
  2. Запускаем bundle install
  3. Теперь мы можем генерировать скаффолд админки при помощи, например, такой команды:
    bin/rails g admin:scaffold_controller Post title:string content:text published:boolean

Поля модели указываются, поскольку используются в контроллере для указания допустимых к изменению атрибутов модели. При этом сама модель данным генератором не создается, для этого можно использовать стандратный генератор.

Стоит также отметить, что для админки целесообразно создать отдельный базовый класс контроллера AdminController, унаследованный от ApplicationController и в него добавить фильтры авторизации, админский лейаут и прочее и уже от него наследовать все остальные контроллеры. Возможно данная опция будет добавлена в гем в будущем.

Буду благодарен за обратную связь.

Список полезной литературы: