Фреймворк Ruby on Rails меня просто очаровывает, но недавнего времени некоторую сложность представляла из себя генерация CRUD контроллеров.
Почти всегда мне было необходимо реализовывать списки с сортировкой, фильтрацией и пагинацией, а стандартного способа этого достичь в рельсах я не обнаружил. Перепробовал несколько вариантов и ни один меня не удовлетворил:
- стандартный генератор scaffold_controller — ничего подобного нет, CRUD с простейшим дизайном
- nifty Scaffold — его разработка приостановлена, но все равно фильтрации и сортировки нет
- гем для DataTable — не прижился, он обеспечивает только представление данных, а код для фильтрации и сортировки пришлось бы писать самому
Долгое время не удавалось найти ничего похожего на полюбившийся мне виджет CGridView из Yii framework. Уже почти смирился с необходимостью писать свой велосипед, но наткнулся на DRY CRUD и хочу поделится опытом его использования. Может кому-то он окажется полезным, а может кто-то подскажет еще более подходящий инструмент.
Установка
DRY CRUD это гем который с помощью генератора создает в проекте свой класс контроллера, после этой генерации гем можно отключать, а сгенерированный им код при необходимости изменять.
Для установки достаточно указать гем в Gemfile
gem 'dry_crud', '= 1.7.0'
запустить
bundle install
и генератор
rails generate dry_crud --templates haml --tests rspec
Демонстрация возможностей
В качестве демонстрации я создал приложение из двух моделей User и Post, связанные отношением один-к-многим.
rails generate model User name:string email:string pass:string
rails generate model Post author_id:integer title:string content:text is_draft:boolean
Для более понятного для человека отображения определил метод to_s и получился такой код моделей:
#user.rb
class User < ActiveRecord::Base
attr_accessible :email, :name, :pass
has_many :posts
def to_s
"#{name}"
end
end
#post.rb
class Post < ActiveRecord::Base
attr_accessible :user_id, :content, :is_draft, :title
belongs_to :user
def to_s
"#{title}"
end
end
после чего в папке /app/controllers достаточно создать файл контроллера и наследовать его от CrudController. Можно также указать по каким столбцам обеспечить фильтрацию.
#users_controller.rb
class UsersController < CrudController
self.search_columns = [:name, :email]
end
Вот и все, не обязательно даже создавать папку для вьюх, пока не потребуется более сложный функционал. Запускаем приложение и получаем работающий CRUD с сортировкой (кликабельные столбцы) и фильтрацией (если в контроллере установлена self.search_columns) а пагинацию можно прикрутить с минимальными усилиями и она будет совместима и с фильтром и с сортировкой.
Форма редактирования тоже не разочаровала, она определяет тип каждого поля и использует соответствующий контрол для boolean, string и text поля, а также для связи «belongs_to»:
и конечно же все это можно настраивать!
Подключение Twitter bootstrap
Много моих проектов используют Twitter bootstrap и адаптация DRY CRUD происходит очень просто, достаточно подключить гем «twitter-bootstrap-rails», установить генератором
rails generate bootstrap:install less
удалить app/views/layouts/crud.html.haml и /app/assets/stylesheets/sample.scss и переписать /app/views/layouts/application.html. Кому интересно, может посмотреть исходник проекта.
В итоге получаем узнаваемый дизайн:
Скрытие колонок
Если например необходимо скрыть колонки created_at и updated_at во всех контроллерах, достаточно изменить хелпер базового класса.
#/app/helpers/list_helper.rb
def default_attrs
attrs = model_class.column_names.collect(&:to_sym)
attrs - [:id, :position, :password, :created_at, :updated_at] #< добавил сюда
end
Изменение набора колонок
Теперь в Users скроем столбец pass и добавим created_at. Для этого необходимо создать папку /app/views/users скопировать в неё общий партиал /app/views/crud/_form.html.haml
В первую и единственную строку = crud_table
добавим список столбцов, которые мы хотим видеть:
= crud_table :name, :email, :created_at
Результат:
Форматирование содержимого ячеек
По умолчанию содержимое постов отображается полностью:
Что бы сделать его обрезку до 100 символов достаточно создать в хелпере метод format_
#/app/helpers/post_helper.rb
module PostHelper
def format_content(post)
truncate(post.content, :length => 100, :omission => '...')
end
end
результат:
Локализация
Менять надписи для полей не изменяя представления можно используя возможности рельсового L18n. Для этого необходимо установить язык по умолчанию:
#/config/application.rb
....
module DryCrudSample
class Application < Rails::Application
...
I18n.default_locale = :ru
end
end
для генерации языковых файлов я воспользовался гемом «l18n_generator»
#Gemfile
group :development do
gem 'i18n_generators'
end
качаем файл локали с репозитория rails-i18n
rails generate i18n_locale ru
копируем сгенерированный при установке DRY CRUD файл перевода /config/locales/en_crud.yml в ru_crud.yml и переводим.
Генерируем YAML файл для наших моделей
rails generate i18n_translation ru
получаем файл /config/locales/translation_ru.yml который я предпочитаю переименовать в ru_models.yml
Остается только написать желаемые названия для полей наших моделей
После рестарта приложения получаем локализированный интерфейс:
Изменение поведения после редактирования
Картинки кончились, начинается более глубокая настройка.
По умолчанию после успешного создания или редактирования записи происходит redirect на просмотр этой записи (действие «show») мне было более привычно, что бы в таком случае редирект происходил на «index» решение:
#/app/controllers/crud_controller.rb
def update(options = {}, &block)
assign_attributes
updated = with_callbacks(:update, :save) { entry.save }
#respond_with(entry, options.reverse_merge(:success => updated), &block) #<- было
respond_with(entry, options.reverse_merge(:success => updated, :location=> index_url), &block) #стало
end
Подобная замена понадобилась и в методе create
Потом в проекте понадобилось после редактирования записи возвращаться на её же форму редактирования. Что бы это реализовать в нашем контроллере Posts всего лишь необходимо переопределить метод update
в котором передавать желаемый URL:
#/app/controllers/posts_controller.rb
def update
super location: edit_post_path(params[:id])
end
Добавление кнопок в столбец действий
На одном из проектов надо было реализовать функцию клонирования записи. Эта функциональность, насколько я понимаю отсутствует по умолчанию, поэтому я реализовывал её следующим образом. На примере Post, сначала определял новое действие в ресурсном маршруте:
#routes.rb
resources :posts do
get :clone, :on => :member
end
Это действие должно будет получать id
копируемой записи и отображать форму создания с уже заполненными полями. Submit формы произойдет на действие create
.
код клонирования модели я разместил в базовом классе:
#/app/controllers/crud_controller.rb
def duplicate_entry()
set_model_ivar( find_entry.dup() )
end
Код генерации кнопки разместился в хелпере:
#/app/helpers/crud_helper.rb
def action_col_clone(table, &block)
action_col(table) do |e|
link_table_action('list-alt', action_path(e, &block)) #тут указывается какое изображение отображать на кнопке
end
end
Код добавления новой кнопки в столбец место в представлении:
#/app/views/posts/_list.html.haml
= crud_table :title, :content, :is_draft do |t|
- action_col_clone t do |e|
- clone_post_path e #тут указывается урл на действие
Все заработало. Добавлено всего две функции, и теперь по всему проекту можно добавить кнопку «клонировать» всего одной строкой!
Вывод
На самом деле с помощью гема «deep_cloneable» дублицировать ActiveRecord можно вместе с её зависимостями. А в форме можно редактировать не только саму запись, но и её связанные через «has_many». А фильтр сделать более сложным, не один textfield а несколько select'ов. И все это достаточно просто, частично описано в документации, частично постигается изучением исходников.
Я пришел к выводу, что DRY CRUD очень гибкий и мощный.
Немного смущает, что для внесения некоторых изменений приходится править «исходный код», хоть он и находится внутри проекта, наверное это может усложнить применение обновлений от автора. Но пока с такой необходимостью я не сталкивался, да и объем изменений невелик, что бы это составляло реальную проблему.
Ссылки
Инструкция по гему DRY CRUD
Исходник примера на GitHub
Автор: charger_lda