Easy Sortable Lists with Rails

  Rails  
 Nov 2019

What’s more fun than dragging and dropping to order stuff? Nothing that this blog will cover anyway.

Draggable lists is easy-peasy with Rails 6, some gems, jquery-ui and webpacker. Here is a quick reference next time you need to do it.

Dragging and dropping feature in rails

There is a good, well maintained Gem to handle ordering items in a model. ActsAsList.

This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a position column defined as an integer on the mapped database table.

First up, add these to your Gemfile.

# Font Awesome, is. Or choose your own icony goodness.
gem 'font-awesome-sass', '~> 5.6', '>= 5.6.1'
gem "acts_as_list"

Bundle that, and add jquery-ui to your webpacker setup.

$ bundle
$ yarn add jquery-ui

Now set up your app to use jquery-ui to allow html elements to be dragged around.


// javascript/packs/application.js

require("jquery-ui/ui/widget")
require("jquery-ui/ui/widgets/sortable")

$(document).on("turbolinks:load", () => {
  $("#contents").sortable({
    handle: '.handle',
    update: function(e, ui) {
      Rails.ajax({
        url: $(this).data("url"),
        type: "PATCH",
        data: $(this).sortable('serialize'),
      });
    }
  });
})

In your view, you need to assign a id to each element in the format item_id. In this case content_1, content_2 etc. Rails has a helper for that, dom_id. You also need to set the data-url so the JS knows where to send the new order to. Note we add a class to the cell with the drag icon so we can tell jquery-ui which element to use to grab the item.

/views/contents/index.html.erb
<tbody id="contents" data-url="<%= sort_contents_path %>">
  <% @contents.each do |content| %>
    <%= content_tag :tr, id: dom_id(content), class: dom_class(content) do %>
      <td class='handle'><%= icon('fas', 'bars') %></td>
      <td><%= link_to content.name, content %></td>  
      ..        
      <td class="actions"><%= link_to "Edit", edit_content_path(content) %></td>
    <% end %>
  <% end %>
</tbody>

Now include your icons:

add to application.scss
@import "font-awesome";

.ui-sortable-handle{
  cursor: pointer;
}

You should at this point be able to drag your items around, but they won’t save.

Next set up your model to be ordered.

# app/models/content.rb
class Content < ApplicationRecord
  acts_as_list
end

run:
rails g migration AddPositionToContent position:integer
rake db:migrate

Now set up your routes and controller. We need to accept a patch to the sort method, which will loop through all the items to sort and assign the new position. Make sure your index method returns the items in the right order too.

/config/routes.rb
resources :contents do
    collection  do
      patch :sort
    end
end

/app/controllers/contents_controller.rb
def index
  # list the items in the right order
  @contents = Content.order(:position)
end

def sort
  # accept the post with all the items to sort.
  params[:content].each_with_index do |id, index|
    Content.where(id: id).update_all(position: index + 1)
  end
  head :ok
end

And kapow! Your list drags, it saves. Things are sorted.


Web Development

Got a web project you need doing well? We can help with that!

Find out more