
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:
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.
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
run
and generator
As a demonstration, I created an application from two User and Post models connected by a one-to-many relationship.
For a more understandable display for humans, the to_s method was defined and the following model code was obtained:
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.
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!
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
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:

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

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
result:

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:
to generate language files, I used the gem "l18n_generator"
download the locale file from the rails-i18n repository
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
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:

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:
A similar replacement was also needed in the method.
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
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:
This action will have to receive the
I placed the model cloning code in the base class:
The button generation code is located in the helper:
The code for adding a new button to the column location in the view:
Everything worked. Only two functions have been added, and now you can add the “clone” button in just one line throughout the project!
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.
Instructions for gem DRY CRUD
Sample source on GitHub
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
update
in 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
id
copied 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