Creating a Ruby on Rails admin form to bulk update multiple models

For my rails photo gallery site I needed the ability to bulk add descriptions and tags to my images. Finding, loading, editing, saving each photo individually is painful. I decided to create a form to edit a page of images at one time.

Added a 'get' route for the form. edited file: config/routes.rb

get 'admin/images-bulk-update' => 'images#bulk_update', :as => 'admin_images_bulk_update'

Added the bulk_update method to the images controller. Edited file: app/controllers/images_controller.rb

class ImagesController < ApplicationController

  def bulk_update
    @images = current_user.images.order("created_at desc").page params[:page]
  end

end

Add the view for the bulk_update action. The form_tag posts to another controller action. The each loop shown below outputs the tabular image data, and the fields_for method uses a partial to render the form fields. file: app/views/images/bulk_update.erb

<%= form_tag admin_images_bulk_update_save_path :method => :post do %>
  <%= hidden_field_tag 'page', (params[:page].nil? ? 1 : params[:page]) %>
  <table class='table table-striped table-condensed'>
    <thead>
      <tr>
        <th>ID</th>
        <th>Thumb</th>
        <th>Title</th>
        <th>Description</th>
        <th>Tags</th>
        <th>Album</th>
      </tr>
    </thead>
    <tbody>
      <% @images.each do |image| %>
        <%= fields_for "images[]", image do |f| %>
          <%= render :partial => 'bulk_update_form_fields', :locals => {:f => f, :image => image} %>
        <% end %>
      <% end %>
    </tbody>
  </table>
  <p><%= submit_tag "Update Images", :class => 'btn btn-success' %></p>
<% end %>

<%= paginate @images %>

The field_for renders the partial: bulk_update_form_fields. This partial simply outputs the form elements. file: app/views/images/_bulk_update_form_fields.html/erb

<tr>
  <td>
    <%= image.id %>
  </td>
  <td>
    <%= image_tag image.upload.url(:thumb) %>
  </td>
  <td>
    <%= f.text_field :title %>
  </td>
  <td>
    <%= f.text_field :description %>
  </td>
  <td>
    <%= f.text_field :tag_list %>
  </td>
  <td>
    <%= image.album.title %>
  </td>
</tr>

The above form submits to "admin_images_bulk_update_save_path". The post route for the form. edited file: config/routes.rb

post 'admin/images-bulk-update-save' => 'images#bulk_update_save', :as => 'admin_images_bulk_update_save'

Added the bulk_update_save method to the images controller. The update method on the Image object accepts the keys and values from the submitted form data to update each Image model. The rest of the code collects errors, and redirects the user back to the form action. edited file: app/controllers/images_controller.rb

class ImagesController < ApplicationController

  def bulk_update_save

    result = Image.update(params[:images].keys, params[:images].values).reject { |p| p.errors.empty? }
    if result.empty?
      flash[:notice] = "Images updated"
      redirect_to admin_images_bulk_update_path(:page => params[:page])
    else
      image_ids = result.collect {|i| i.id}
      flash[:error] = "Error(s) occurred updating image(s): #{image_ids.join ', '}"
      redirect_to admin_images_bulk_update_path(:page => params[:page])
    end

  end

end

Last I added a before_filter method to authenticate admin users for these controller actions. edited file: app/controllers/images_controller.rb

class ImagesController < ApplicationController

  before_filter :auth_bulk_update, :only => [:bulk_update, :bulk_update_save]

  private

    def auth_bulk_update
      # authentication code here
    end

end

The end result is a form that allows me to bulk edit my photos with pagination.

images bulk edit form