DRY your Rails CRUD with Simple Form and Inherited Resources

When you're writing a Rails application you usually end up with a lot of CRUD-only controllers and views just for managing models as an admin. Your user-facing views and controllers should of course have a lot of thought and care put into their design, but for admin stuff you just want to put data in the database as simply as possible. Rails of course gives you scaffolds, but that's quite a bit of duplicated code. Instead, you could use the one-two-three combination of Simple Form, Inherited Resources, and Rails' built-in template inheritance to DRY up most of the scaffolding while still preserving your ability to customize where appropriate. This lets you build your admin interface without having to resort to something heavy like Rails Admin or ActiveAdmin while also not having to build from scratch every time.

Inherited Resources consists of a base controller you can inherit from that implements all of the standard resourceful actions, plus a few convenience things for working with this controller. If you have a Book model, you could create a complete resourceful controller for it with the following code:

class BooksController < InheritedResources::Base
  protected
  def permitted_params
    params.permit(book: {:title, :author, :isbn})
  end
end

As you can see, Inherited Resources has automatic integration with Rails 4's Strong Parameters.

DRY up your CRUD

Rails 3.1 and further shipped with a thing called 'template inheritance'. This simply means that there's a default search path for templates that Rails will hunt through to find a suitable template. If there's no index.html.erb in app/views/books, for example, Rails will look in app/views/application because that's BooksController's base class. We can use this to construct default views for our CRUD controllers. First, let's make a base class for our controllers to inherit from:

class CrudController < InheritedResources::Base
  def attrs_for_index
    []
  end

  def attrs_for_form
    []
  end

  helper_method :attrs_for_index
  helper_method :attrs_for_form
end

And now we can set up default views. First, app/views/crud/index.html.erb:

<h1>
  <%= resource_class.to_s.pluralize %>&nbsp;
  <small>
  <%= link_to 'New', [:new, resource_class.to_s.downcase.to_sym] %>
</h1>
<table>
  <thead>
    <tr>
      <% attrs_for_index.each do |attr| %>
        <th><%= attr.to_s.titlecase %></th>
      <% end %>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <% collection.each do |resource| %>
      <tr>
      <% attrs_for_index.each do |attr| %>
        <td><%= link_to resource.attributes[attr.to_s], resource %></td>
      <% end %>
      <td><%= link_to 'Edit', [:edit, resource] %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Inherited Resources gives you a few helpers that make these views really easy:

  • collection maps to the collection of objects in your controller. If it was BooksController, collection would return @books. This is only present in the index view.
  • resource_class returns the class of the resource your controller is managing
  • resource maps to @book and is available in every view except index.

Now we need a show view. Most of the time, all you want to see is a dump of the resource's attribute, which is exactly what we're going to do in app/views/crud/show.html.erb:

<h1>
  <%= resource_class %> <%= resource.id %>
  <small><%= link_to 'Edit', [:edit, resource] %></small>
</h1>
<table>
  <tr>
    <th>Key</th>
    <th>Value</th>
  </tr>
  <% resource.attributes.sort.each do |key, value| %>
  <tr>
    <td><%= key %></td>
    <td><%= value %></td>
  </tr>
  <% end %>
</table>

The edit and new views are super simple:

<h1>New <%= resource_class.to_s.titlecase %></h1>
<%= render 'form' %>
<h1>Editing <%= resource_class.to_s.titlecase %> <%= resource.id %></h1>
<%= render 'form' %>

Let's look at app/views/crud/_form.html.erb:

<%= simple_form_for resource do |f| %>
  <% attrs_for_form.each do |attr| %>
    <%= f.input attr %>
  <% end %>
  <%= f.button :submit %>
<% end %>

Here's where Simple Form really starts to shine. It auto-detects the proper form input to display by inspecting the attribute, so we don't have to do any work for a basic form. Of course, because we're using view inheritance, if you want to make a more complicated for you can just drop what you want into app/views/<controller>/_form.html.erb and it'll get picked up automatically. That also goes the same for any of the templates.

We should flesh out BooksController with overrides for those attr methods:

class BooksController < CrudController
  def attrs_for_index
    [:title, :author, :isbn]
  end

  def attrs_for_form
    [:title, :author, :isbn]
  end
end

Sorting, Paging, and Whatnot

You're probably thinking that this whole thing is great, but what if you need to, for example, sort or paginate the results on the index? Inherited Resources has you covered. Just override the collection method, like this:

class BooksController < CrudController
  def collection
    @books ||= end_of_resource_chain.order('created_at DESC')
  end
end

The end_of_resource_chain method gives you your resource relation after applying all of the other neat things that Inherited Resources can do. Check out the README for more details on that.

In the same vein, what if you want to limit the objects created and accessed by the rest of the CRUD actions? For example, let's say you want to limit the books available to the current user:

class BooksController < CrudController
  def begin_of_association_chain
    current_user
  end
end

begin_of_association_chain is where Inherited Resources starts out building objects. If you don't provide it, it defaults to the resource class.


There's a lot more you can do with Inherited Resources and Simple Form, like build controllers that deal with multiple nested resources and forms that have automatically populated select dropdowns. You should check them out.

Posted in: Software  

Tagged: Programming Rails