Get rid of code repetition with DRY CRUD

The Ruby on Rails framework just fascinates me, but until recently, the generation of CRUD controllers was a bit of a challenge.

Almost always, I needed to implement lists with sorting, filtering and pagination, and I did not find a standard way to achieve this in rails. I tried several options and not a single one satisfied me:
  • standard scaffold_controller generator - there is nothing like it, CRUD with a simple design
  • nifty Scaffold - its development is paused, but there is no filtering and sorting anyway
  • gem for DataTable - did not take root, it provides only a representation of the data, and you would have to write code for filtering and sorting yourself

For a long time, I could not find anything like the CGridView widget from the Yii framework that I liked. Already almost resigned to the need to write your bike, but stumbled upon DRY CRUD and I want to share the experience of its use. Maybe someone will find it useful, or maybe someone will tell you an even more suitable tool.

Installation


DRY CRUD is a gem which, using the generator, creates its own controller class in the project, after this generation the gem can be turned off, and the code generated by it can be changed if necessary.
To install, just specify the gem in the Gemfile
gem 'dry_crud', '= 1.7.0'

run
bundle install

and generator
rails generate dry_crud --templates haml --tests rspec


Opportunity demonstration


As a demonstration, I created an application from two User and Post models connected by a one-to-many relationship.
rails generate model User name:string email:string pass:string
rails generate model Post  author_id:integer title:string content:text is_draft:boolean

For a more understandable display for humans, the to_s method was defined and the following model code was obtained:
#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

then in the / app / controllers folder it’s enough to create a controller file and inherit it from CrudController . You can also specify which columns provide filtering.
#users_controller.rb
class UsersController < CrudController
  self.search_columns = [:name, :email]
end

That's all, it is not necessary to even create a folder for the view until you need more complex functionality. We launch the application and get a working CRUD with sorting (clickable columns) and filtering (if self.search_columns is installed in the controller ) and pagination can be screwed with minimal effort and it will be compatible with both filter and sorting.


The editing form also did not disappoint, it determines the type of each field and uses the appropriate control for the boolean, string and text fields, as well as for the belongs_to link:

and of course, all this can be configured!

Twitter bootstrap connection

Many of my projects use Twitter bootstrap and DRY CRUD adaptation is very simple, just connect the twitter-bootstrap-rails gem , install it with a generator
rails generate bootstrap:install less

delete app / views / layouts / crud.html.haml and /app/assets/stylesheets/sample.scss and rewrite /app/views/layouts/application.html . Anyone interested can see the source of the project.
As a result, we get a recognizable design:


Hide columns

If for example you need to hide the created_at and updated_at columns in all controllers, just change the base class helper.
#/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


Change Column Set

Now in Users, hide the pass column and add created_at . To do this, create the folder / app / views / users and copy the general partial /app/views/crud/_form.html.haml into it. Add the list of columns that we want to see
in the first and only line = crud_table:
= crud_table :name, :email, :created_at

Result:


Formatting cell contents

By default, the contents of posts are displayed in full:

To make it trim to 100 characters, just create the format_ method in the helper
#/app/helpers/post_helper.rb
module PostHelper
  def format_content(post)
    truncate(post.content, :length => 100, :omission => '...')
  end
end

result:


Localization

You can change the labels for the fields without changing the view using the capabilities of the rail L18n. To do this, set the default language:
#/config/application.rb
....
module DryCrudSample
  class Application < Rails::Application
     ...
    I18n.default_locale = :ru
  end
end


to generate language files, I used the gem "l18n_generator"
#Gemfile
group :development do
  gem 'i18n_generators'
end


download the locale file from the rails-i18n repository
rails generate i18n_locale ru


copy the translation file /config/locales/en_crud.yml generated during the installation of DRY CRUD into ru_crud.yml and translate.
Generate a YAML file for our models
rails generate i18n_translation ru

we get the file /config/locales/translation_ru.yml which I prefer to rename to ru_models.yml
It remains only to write the desired names for the fields of our models

After restarting the application, we get the localized interface:


Behavior change after editing

Pictures are over, a deeper adjustment begins.
By default, after successfully creating or editing an entry, a redirect occurs to view this entry (the “show” action), I was more accustomed to the redirect to the “index” solution:
#/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

A similar replacement was also needed in the method. create

Then in the project, after editing the record, it was necessary to return to its editing form. To implement this in our Posts controller, you just need to redefine the method updatein which to transmit the desired URL:
#/app/controllers/posts_controller.rb
  def update
    super location: edit_post_path(params[:id])
  end


Adding buttons to the action column

On one of the projects, it was necessary to implement the recording cloning function. This functionality, as I understand it, is missing by default, so I implemented it as follows. For example, Post, first defined a new action in the resource route:
#routes.rb
  resources :posts do
    get :clone, :on => :member
  end

This action will have to receive the idcopied record and display the creation form with the fields already filled. Submit forms will happen on action create.
I placed the model cloning code in the base class:
#/app/controllers/crud_controller.rb
  def duplicate_entry()
    set_model_ivar( find_entry.dup()  )
  end


The button generation code is located in the helper:
#/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


The code for adding a new button to the column location in the view:
#/app/views/posts/_list.html.haml
= crud_table :title, :content, :is_draft do |t|
  - action_col_clone t do |e|
    - clone_post_path e #тут указывается урл на действие

Everything worked. Only two functions have been added, and now you can add the “clone” button in just one line throughout the project!

Conclusion


Actually, using the "deep_cloneable" gem, you can duplicate ActiveRecord along with its dependencies. And in the form you can edit not only the record itself, but also its related ones through "has_many". And to make the filter more complex, not just one textfield, but several selects. And all this is quite simple, partially described in the documentation, partially comprehended by the study of source codes.

I came to the conclusion that DRY CRUD is very flexible and powerful.

It is a little embarrassing that in order to make some changes, you have to edit the "source code", although it is inside the project, probably this can complicate the use of updates from the author. But so far I have not encountered such a need, and the volume of changes is small, so that this would constitute a real problem.

References


Instructions for gem DRY CRUD
Sample source on GitHub

Also popular now: