Rails bulk delete items on frontend part

So when your database grows it might come in handy if you give your customers the option to bulk manipulate data in lists. I’ve got a list of products and my client told me, that they often create dozens of test items to check out things and then they delete them. Clicking one by one and, especially, waiting every time for the page to load can be tiresome. Here is my approach:

Wrap the listing table in a form

=form_tag admin_bulk_delete_path(sublevel: params[:sublevel], :id => "bulk-edit-form") do
          =hidden_field_tag :object, object_name(object)
          .table-responsive          
            %table.table.table-striped#listing_table
              %thead
                %tr
                  %th
                  -if object.has_attribute? :url
                    %th
                      URL
                  %th.text-right
                    =t('admin.form.actions.main')
              %tbody
                -object.each do |obj|
                  %tr
                    %td.checkbox_column
                      =check_box_tag "object_ids[]", obj.id, false, class: "i-checks bulk_actionable"
                    -if obj.respond_to?(:url)
                      %td
                        =obj.url
                    %td
                      =time_ago_in_words(obj.created_at)
                      =t('admin.form.created_ago')
                    %td.text-right
                      .btn-group
                        =link_to t('admin.form.actions.edit.verb'), send("edit_admin_#{local_assigns[:namespace]}#{object_name(object)}_path", obj), class: "btn-white btn btn-xs", id: "edit_list_item_#{obj.id}"
                        =link_to t('admin.form.actions.delete'), send("admin_#{local_assigns[:namespace]}#{object_name(object)}_path", obj), method: :delete, data: {confirm: t('admin.form.confirm.main') }, class: "btn-white btn btn-xs", id: "delete_list_item_#{obj.id}"
          .row
            .col-sm-12.m-b-xs
              .pull-left
                #bulk-actions
                  = submit_tag t('admin.form.actions.bulk_destroy'), class: "btn btn-xs btn-danger", id: "bulk-delete", data: {confirm: t('admin.form.confirm.bulk_delete')}

Don’t get confused by my ‘object’… err… object, it serves the purpose of enabling many models to use this listing partial. The main parts are ‘form_for’, the checkbox and the ‘submit_tag’. It is important to use an array for storing the checkbox values, or else we won’t be able to retrieve all id’s at once.

Now the form leads us to the custom controller (‘rails g controller bulk_actions’):

class Admin::BulkActionsController < AdminController

  def bulk_delete
    object = params[:object]
    if params[:object_ids].is_a?(Array) && params[:object_ids].length > 1  #let's make sure we got what we expected
      @objects = object.singularize.capitalize.constantize.find(params[:object_ids])
      @objects.each do |object|
        object.destroy
      end
      flash[:success] = t('admin.flash.success.delete')
      redirect_to send("admin_#{object.pluralize}_path", sublevel: params[:sublevel])
    else
      flash[:danger] = t('admin.flash.fail.delete')
      redirect_to send("admin_#{object.pluralize}_path", sublevel: params[:sublevel])
    end
  end
end

First of all, I’m getting the object from ‘params[:object]’ (this is why I included the hidden_field_tag in my form). Since I’m using the partial for any model, I need to know, do I delete the Post model or the Product model right now. The if-clause makes sure we only handle a bunch of items, it does not make any sense to bulk delete 1 item. Since my object is rather a string, I need to convert it to a modal constant. Then we simply iterate over each found object and destroy it. You can disregard the ‘sublevel’ part.

Now for the frontend – I wanted the delete button only to appear if two or more items are selected and be hidden elsewise. Since I’m using iChech checkboxes we need to hook up on different methods. Particularly it is ‘ifToggled’ in this case:

$(document).ready(function(){
  $('input').on('ifToggled', function(event){
      var check_count = $('input:checked').length;  //count the number of checked elements
      if( check_count > 1 ) {
        $("#bulk-actions").fadeIn(400).show();
      } else {
        $("#bulk-actions").fadeOut(400).hide();
      }
  });    
});

Add one last thing – the route

post 'bulk_delete', to: 'bulk_actions#bulk_delete'

And you are ready to go!

Heavily inspired by this article.

Published by

Anton

Hello! My name is Anton. I am a passionate project manager who loves digging deep into code. You can check my Github and CodeEval. Hopefully my thoughts on management can lead you to one or another good idea.