Избавляемся от повторения кода с помощью DRY CRUD

от автора

Фреймворк 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

ссылка на оригинал статьи http://habrahabr.ru/post/175887/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *