dry_crud

Generates DRY and specifically extendable CRUD controller, views and helpers for Rails applications

MIT License

Downloads
95.4K
Stars
158
Committers
7

= DRY CRUD

{rdoc-image:https://github.com/codez/dry_crud/actions/workflows/build.yml/badge.svg}[https://github.com/codez/dry_crud/actions/workflows/build.yml] {rdoc-image:https://api.codeclimate.com/v1/badges/ef7488764a0d9805b37d/maintainability}[https://codeclimate.com/github/codez/dry_crud/maintainability] {rdoc-image:https://api.codeclimate.com/v1/badges/ef7488764a0d9805b37d/test_coverage}[https://codeclimate.com/github/codez/dry_crud/test_coverage]

dry_crud generates simple and extendable controllers, views and helpers that support you to DRY up the CRUD code in your Rails projects. List, search, sort, show, create, edit and destroy any model entries in just 5 minutes. Start with these artifacts and build a clean base to efficiently develop your application upon.

== Installation

Create your Rails application directly with the dry_crud application template:

rails new APP_NAME -m https://raw.github.com/codez/dry_crud/master/template.rb

If your application already exists or you prefer the DIY way, then install the Gem (gem install dry_crud), add it to your Gemfile and run the generator. You may remove the Gemfile entry again afterwards, it is not required anymore.

rails generate dry_crud [--templates haml] [--tests rspec]

By default, dry_crud generates ERB templates and Test::Unit tests. Pass the options above to generate HAML templates and/or RSpec examples instead.

We recommend to use dry_crud as a generator as described above for the best understanding and the most flexibility. When you are familiar with dry_crud, it is now also possible to use it directly as a Rails Engine. Simply add the gem to your Gemfile. You may still generate single files to adapt them:

rails generate dry_crud:file list/index.html.erb

If a dry_crud file exists in your application, it will be used, if not, the one from the engine is used. This holds for controllers, helper methods and view templates.

== Integration

To integrate dry_crud into your code, only a few additions are required:

  • For uniform CRUD functionality, just subclass your controllers from CrudController and define the +permitted_attrs+ (for StrongParameters).
  • Overwrite the +to_s+ method of your models for a human-friendly representation in captions.
  • Optionally define a +list+ scope in your models to be used in the +index+ action.
  • Optionally define a +options_list+ scope in your models to be used in select dropdowns.

From version 5.0 onwards, the major and minor version numbers will be kept in sync with Rails, and only the matching Rails version is supported.

== Background

In most Rails applications, you have some models that require basic CRUD (create, read, update, delete) functionality. There are various possibilities like Rails scaffolding, {Inherited Resources}[https://github.com/activeadmin/inherited_resources] or {Rails Admin}[https://github.com/railsadminteam/rails_admin]. Still, various parts in your application remain duplicated. While you might pull up common methods into a common superclass controller, most views still contain very similar code. And then you also have to remember the entire API of these frameworks.

Enter dry_crud.

dry_crud is a Rails generator. All code resides in your application and is open for you to inspect and to extend. You may pick whatever you consider useful or adapt what is not sufficient. Even if you do not require any CRUD functionality, you might find some helpers simplifying your work. There are no runtime dependencies to the dry_crud gem. Having said this, dry_crud does not want to provide a maximum of functionality that requires a lot of configuration, but rather a clean and lightweight foundation to build your application's requirements upon. This is why dry_crud comes as a generator and not as a Rails extension.

dry_crud does not depend on any other gems, but easily allows you to integrate them in order to unify the behavior of your CRUD controllers. You might even use the gems mentioned above to adapt your generated CrudController base class. All classes come with thorough tests that provide you with a solid foundation for implementing your own adaptions.

A basic CSS gets you started with your application's layout. For advanced needs, dry_crud supports the styles and classes used in {Bootstrap}[http://getbootstrap.com]. As the views are generated into your application code, you are free to change the styling to your needs.

If you find yourself adapting the same parts of dry_crud for your applications over and over, please feel free to {fork me on Github}[http://github.com/codez/dry_crud].

See the Examples section for some use cases and the Generated Files section below for details on the single classes and templates.

== Examples

=== Controller with CRUD functionality

Say you want to manage a +Person+ model. Overwrite the +to_s+ method of your model for a human-friendly representation used in page titles.

app/models/person.rb: class Person def to_s [lastname, firstname].compact.join(' ') end end

Then create the following controller. The +permitted_attrs+ define the attribute parameters allowed when creating or updating a model entry (see {Strong Parameters}[http://api.rubyonrails.org/classes/ActionController/StrongParameters.html]).

app/controllers/people_controller.rb: class PeopleController < CrudController self.permitted_attrs = [:firstname, :lastname, :birthday, :gender, :city_id] end

That's it. You have a sortable overview of all people, detail pages and forms to edit and create people. Of course, you may delete people as well. By default, all attributes are displayed and formatted according to their column type wherever they appear. This applies for the input fields as well.

==== Customize single views

Well, maybe there are certain attributes you do not want to display in the people list, or others that are not editable in the form. No problem, simply create a _list partial in app/views/people/_list.html.erb to customize this:

<%= crud_table :lastname, :firstname, :city, :gender %>

This only displays these four attributes in the table. All other templates, as well as the main index view, fallback to the ones in app/views/crud.

==== Adapt general behavior

Next, let's adapt a part of the general behavior used in all CRUD controllers. As an example, we include pagination with kaminari[https://github.com/kaminari/kaminari] in all our overview tables:

In app/controllers/list_controller.rb, change the list_entries method to def list_entries model_scope.page(params[:page]) end

In app/views/list/index.html.erb, add the following line for the pagination links: <%= paginate entries %>

And we are done. All our controllers inheriting from ListController, including above +PeopleController+, now have paginated index views. Because our customization for the people table is in the separate _list partial, no further modifications are required.

==== Special formatting for selected attributes

Sometimes, the default formatting provided by +format_attr+ will not be sufficient. We have a boolean column +female+ in our model (which is quite a legacy nowadays!), but we would like to display 'male' or 'female' for it (instead of 'no' or 'yes', which is a bit cryptic). Just define a method in your view helper starting with format_, followed by the class and attribute name:

In app/helpers/people.rb: def format_person_female(person) person.female ? 'female' : 'male' end

Should you have attributes with the same name for multiple models that you want to be formatted the same way, you may define a helper method format_{attr} for these attributes.

By the way: The method +f+ in FormatHelper uniformly formats arbitrary values according to their class.

==== Sorting and filtering the index list

The entries listed on the index page are automatically sortable by each displayed database column. To apply a default sorting order, define a +list+ scope in your model:

In app/models/person.rb: scope :list, -> { order('lastname, firstname').includes(:city) }

Alternatively, set the following class attribute in the controller:

In app/controllers/people_controller.rb: self.default_sort = 'lastname, firstname'

When you display computed values in your list table, you may define sort mappings to enable sorting of these columns:

In app/controllers/people_controller.rb: self.sort_mappings = { age: 'birthday', city_id: 'cities.name' }

There is also a simple search functionality (based on SQL LIKE queries) implemented in Crud::Searchable. Define an array of columns in your controller's +search_columns+ class variable to make the entries searchable by these fields:

In app/controllers/people_controller.rb: self.search_columns = [:firstname, :lastname]

If you have search columns defined, a search box will be displayed in the index view that enables filtering of the displayed entries.

=== Standard Tables and Forms

dry_crud provides two builder classes for update/create forms and tables for displaying entries of one model. They may be used all over your application to DRY up the form and table code. Normally, they are used with the corresponding methods from TableHelper and FormHelper. In there are generic helper methods (+plain_table+ and +plain_form+/+standard_form+) and slightly enhanced ones for views of subclasses of CrudController (+crud_table+ and +crud_form+).

==== Tables

The following code defines a table with some attribute columns for a list of same-type entries. Columns get a header corresponding to the attribute name: <%= plain_table(@people) do |t| t.sortable_attrs(:lastname, :firstname) end %>

If entries is empty, a basic 'No entries found' message is rendered instead of the table.

To render custom columns, use the +col+ method with an appropriate block: <%= plain_table(@people) do |t| t.sortable_attrs(:lastname, :firstname) t.col('', class: 'center') { |entry| image_tag(entry.picture) } t.attr(:street) t.col('Map') { |entry| link_to(entry.city, "http://maps.google.com/?q=#{entry.city}") } end %>

For views of subclasses of ListController, you can directly use the +crud_table+ helper method, where you do not have to pass the @people list explicitly and actions are added automatically.

==== Forms

Forms work very similar. In the most simple case, you just have to specify which attributes of a model to create input fields for, and you get a complete form with error messages, labeled input fields according the column types and a save button:

<%= standard_form(@person, :firstname, :lastname, :age, :city) -%>

Of course, custom input fields may be defined as well: <%= standard_form(@person, url: custom_update_person_path(@person.id)) do |f| %> <%= f.labeled_input_fields :firstname, :lastname %> <%= f.labeled(:female) do %> <%= f.radio_button :female, true %> female <%= f.radio_button :female, false %> male <% end %> <%= f.labeled_integer_field :age %> <%= f.labeled_file_field :picture %> <% end %>

Even +belongs_to+ associations are automatically rendered with a select field. By default, entries returned from the +options_list+ scope of the associated model are used as options (if defined, all otherwise). To customize this, either define an instance variable with the same name as the association in your controller, or pass a list option: <%= f.belongs_to_field :hometown, list: City.where(country: @person.country) %>

Yes, it's bad practice to use finder logic in your views! Define the variable @hometowns in your controller instead (as shown in the example above), and you do not even have to specify the list option.

Optionally, +has_and_belongs_to_many+ and +has_many+ associations can be rendered with a multi-select field. Similar to a +belongs_to+ association, all entries from the associated model are used, but can be overwritten using the list option: <%= f.has_many_field :visited_cities, list: City.where(is_touristic: true) %>

And yes again, the same advice for where to put finder logic applies here as well.

Note: +has_and_belongs_to_many+ and +has_many+ associations are not automatically rendered in a form, you have to explicitly include these attributes. You might also want to stylize the multi-select widget with a JavaScript library of your choice.

=== Nested Resources

In case you define nested resources, your CrudController subclass should know. Listing and creating entries as well as displaying links for these resources is dependent on the nesting hierarchy. This is how you declare the namespaces and parent resources in your controller:

self.nesting = :my_namspace, ParentResource

This declaration is for a controller nested in +parent_resources+ within a +:my_namespace+ scope. +ParentResource+ is the corresponding +ActiveRecord+ model. The request param +:parent_resource_id+ is used to load the parent entry, which in turn is used to filter the entries listed and created in your controller. For all parent resources, a corresponding instance variable is created.

The Crud::Nestable module defines this basic behaviour. For more complex setups, have a look there and adjust it to your needs.

=== CRUD controller callbacks

As a last example, let's say we have added a custom input field that must specially processed. Instead of overwriting the entire update action, it is possible to register callbacks for the +create+, +update+, +save+ (= +create+ and +update+) and +destroy+ actions. They work very similarliy like the callbacks on ActiveRecord. For each action, before and after callbacks are run. Before callbacks may also prevent the action from being executed when returning false. Here is some code:

In app/controllers/people_controller.rb: after_save :upload_picture before_destroy :delete_picture

def upload_picture store_file(params[:person][:picture]) if params[:person][:picture] end

def delete_picture if !perform_delete_picture(entry.picture) flash[:alert] = 'Could not delete picture' throw :abort end end

Beside these "action" callbacks, there is also a set of +before_render+ callbacks that are called whenever a certain view is rendered. They are available for the +index+, +show+, +new+, +edit+ and +form+ (= +new+ and +edit+) views. These callbacks are not only called for the corresponding action, but, for example, also when the +new+ view is going to be rendered from an unsuccessfull +create+ action. Say you need to prepare additional variables whenever the form is rendered:

In app/controllers/people_controller.rb: before_render_form :set_hometowns

def set_hometowns @hometowns = City.where(country: entry.country) end

=== Internationalization (I18N)

All text strings used are externalized to an english locale yaml. The keys are organized by controller and template name plus a generic global scope.

To represent your controller hierarchy, a special translation helper +ti+ looks up keys along the hierarchy in the following order: {controller_name}.{template_name}.{key} {controller_name}.{action_name}.{key} {controller_name}.global.{key} {parent_controller_name}.{template_name}.{key} {parent_controller_name}.{action_name}.{key} {parent_controller_name}.global.{key} ... global.{key}

In order to change the title for your +PeopleController+'s +index+ action, you do not need to override the entire template, but simply define the following key: people.index.title = "The People"

Otherwise, the lookup for the title would fallback on the ListController's key list.index.title.

This lookup mechanism also allows you to easily define per-controller overridable text snippets in your views.

=== Example Code

To see an example application built on dry_crud, have a look at {these directories}[https://github.com/codez/dry_crud/tree/master/test/templates/app]. Only certain methods and templates are overriden, all the 'missing' files are provided by dry_crud.

== Generated Files

All generated files are supposed to provide a reasonable foundation for the CRUD functionality. You are encouraged to adapt them to fit the needs of your application. They're yours!

=== Controller

{controller/crud_controller.rb}[https://rubydoc.info/github/codez/dry_crud/master/CrudController]:: Abstract controller providing basic CRUD actions. This implementation mainly follows the one of the Rails scaffolding controller and responses to HTML and JSON requests. Some enhancements were made to ease extendability. Several protected helper methods are there to be (optionally) overriden by subclasses. With the help of additional callbacks, it is possible to hook into the action procedures without overriding the entire method. This class is based on ListController.

{controller/list_controller.rb}[https://rubydoc.info/github/codez/dry_crud/master/ListController]:: Abstract controller providing a basic list action. Use this controller if you require read-only functionality. It includes the following modules.

{controller/dry_crud/generic_model.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/GenericModel]:: Work with the model whose name corrsponds to the controller's name.

{controller/dry_crud/nestable.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Nestable]:: Provides functionality to easily nest controllers/resources.

{controller/dry_crud/rememberable.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Rememberable]:: Remembers certain params of the index action in order to return to the same list after an entry was viewed or edited.

{controller/dry_crud/searchable.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Searchable]:: Search functionality for the index table.

{controller/dry_crud/sortable.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Sortable]:: Sort functionality for the index table.

{controller/dry_crud/render_callbacks.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/RenderCallbacks]:: Provide +before_render+ callbacks to controllers.

{controller/dry_crud/responder.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Responder]:: Responder used by the CrudController to handle the +path_args+.

=== Helpers

{helpers/dry_crud/form/builder.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Form/Builder]:: A form builder that automatically selects the corresponding input type for ActiveRecord columns. Input elements are rendered together with a label by default.

{helpers/dry_crud/form/control.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Form/Control]:: Representation of a single form control consisting of a label, input field, addon or help text.

{helpers/dry_crud/table/builder.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Table/Builder]:: A helper object to easily define tables listing several rows of the same data type.

{helpers/dry_crud/table/col.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Table/Col]:: Helper class representing a single table column.

{helpers/dry_crud/table/actions.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Table/Actions]:: Module to add support for uniform CRUD actions in tables.

{helpers/dry_crud/table/sorting.rb}[https://rubydoc.info/github/codez/dry_crud/master/DryCrud/Table/Sorting]:: Module to add support for sort links in table headers.

{helpers/form_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/FormHelper]:: Create forms to edit models with Crud::FormBuilder. Contains a standardized and a custom definable form.

{helpers/table_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/TableHelper]:: Create tables to list multiple models with Crud::TableBuilder. Contains a standardized and a custom definable table.

{helpers/format_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/FormatHelper]:: Format attribute and basic values according to their database or Ruby type.

{helpers/actions_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/ActionsHelper]:: Uniform action links for the most common crud actions.

{helpers/i18n_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/I18nHelper]:: Translation helpers extending Rails' +translate+ helper to support translation inheritance over the controller class hierarchy.

{helpers/utility_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/UtilityHelper]:: View helpers for basic functions used in various other helpers.

=== Views

All templates in the +list+ and +crud+ folders may be 'overriden' individually in a respective view folder. Define the basic structure of your CRUD views here and adapt it as required for each single model. Actually, the _list.html.erb partial from the +list+ folder gets overriden in the +crud+ folder already.

All templates are available as HAML as well.

==== List

views/list/index.html.erb:: The index view displaying a sortable table with all entries. If you have +search_columns+ defined for your controller, then a search box is rendered as well.

views/list/_list.html.erb:: A partial defining the table in the index view. To change the displayed attributes for your list model, just create an own _list.html.erb in your controller's view directory.

views/list/_search.html.erb:: A partial defining a simple search form that is displayed when +search_columns+ are defined in a subclassing controller.

views/list/_actions_index.html.erb:: The action links available in the index view. None by default.

==== Crud

views/crud/show.html.erb:: The show view displaying all the attributes of one entry and the various actions to perform on it.

views/crud/_attrs.html.erb:: A partial defining the attributes to be displayed in the show view.

views/crud/_list.html.erb:: A partial defining the table in the index view with various links to manipulate the entries.

views/crud/new.html.erb:: The view to create a new entry.

views/crud/edit.html.erb:: The view to edit an existing entry.

views/crud/_form.html.erb:: The form used to create and edit entries. If you would like to customize this form for various models, just create an own _form.html.erb in your controller's view directory.

views/crud/_actions_index.html.erb:: The action links available in the index view.

views/crud/_actions_show.html.erb:: The action links available in the show view.

views/crud/_actions_edit.html.erb:: The action links available in the edit view.

==== Various

views/shared/_labeled.html.erb:: Partial to define the layout for an arbitrary content with a label.

views/shared/_error_messages.html.erb:: Partial to display the validation errors in Rails 2 style.

views/layouts/application.html.erb:: An example layout showing how to use the @title and +flash+.

views/layouts/_flash.html.erb:: An simple partial to display the various flash messages. Included from crud.html.erb.

views/layouts/_nav.html.erb:: An empty file to put your navigation into. Included from crud.html.erb.

app/assets/stylesheets/crud.scss:: A simple SCSS with all the classes and ids used in the CRUD code.

app/assets/images/action/*.png:: Some sample action icons from the {Open Icon Library}[http://openiconlibrary.sourceforge.net].

=== Tests

test/support/crud_test_model.rb:: A dummy model to run CRUD tests against.

{test/support/custom_assertions.rb}[https://rubydoc.info/github/codez/dry_crud/master/CustomAssertions]:: A handful of convenient assertions. Include this module into your test_helper.rb file.

{test/support/crud_controller_test_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/CrudControllerTestHelper]:: A module to include into the functional tests for your CrudController subclasses. Contains a handful of CRUD functionality tests for the provided implementation. So for each new CRUD controller, you get 20 tests for free.

test/controllers/crud_test_models_controller_test.rb:: Functional tests for the basic CrudController functionality.

test/helpers/*_test.rb:: Tests for the provided helper implementations and a great base to test your adaptions of the CRUD code.

=== Specs

spec/support/crud_controller_examples.rb:: A whole set of shared exampled to include into your controller specs. See spec/controllers/crud_test_models_controller_spec.rb for usage. So for each new CRUD controller, you get all the basic specs for free.

{spec/support/crud_controller_test_helper.rb}[https://rubydoc.info/github/codez/dry_crud/master/CrudControllerTestHelper/ClassMethods]:: Convenience methods used by the crud controller examples.

spec/support/crud_test_model.rb:: A dummy model to run CRUD tests against.

spec/controllers/crud_test_models_controller_spec.rb:: Controller specs to test the basic CrudController functionality.

spec/helpers/*_spec.rb:: The specs for all the helpers included in dry_crud and a great base to test your adaptions of the CRUD code.