Skip to main content
39 Common Features:

Sortable Drag and Drop

Episode 207 · September 19, 2017

Drag and drop sorting is pretty easy to add to any Rails application using jQuery sortable



What's up guys? This episode we are diving into creating a sortable list in your rails application, so we want to be able to drag and drop these quiz questions anywhere in this list and we want that to then persist server side, we're going to accomplish this with two gem, number one is access list, it's a really great gem for keeping track of the positions, and it gives you some methods in the ruby side of things to move an item up one or down one and it helps automatically assign positions. The other thing that we're going to be using is jQuery sortable, this comes from jQuery UI, and it's really really easy for us to just say: Hey, this element on the page is sortable, and whenever you're done sorting, then hit this endpoint and we'll make an AJAX request and that's it, so it's really really nice and that's what we're going to be diving into today, so let's take a look at how to get all of this set up.

As I mentioned, we're going to be using jQuery UI rails and access list, so you're going to want to save those gems to your gemfile and then run bundle install to install them, and then access list, if you're not familiar, it has a migration to add a position, so for example you want to create a migration to add position to whatever model you're going to add that to. For ours, it's a question, so we're going to generate a migration rails g migration AddPositionToQuestions position:integer. This is going to be nil by default, but our access list is going to take care of that for us, so that's going to be what we need.

The other thing we need to do for set up is go into our JavaScript application.js and then we need to add

//= require jquery-ui/widget 
//= require jquery-ui/sortable 

That will come after jQuery's set up, so that will then give us access to the sortable functionality with jQuery. Don't forget to restart your rails server after you ran bundle to make sure that the jQuery UI stuff is loadable from the gem, but we're then going to create a new JavaScript file, I'm going to call it questions.js, we're going to get rid of the here, so we're going to say rm app/assets/javascripts/ and we'll create app/assets/assets/javascripts/questions.js here. We're going to have:


document.addEventListener("turbolinks:load", function() {


This is where we're going to set up the sortable stuff, so our index for the questions needs to have some sort of id on the questions list that we're going to have to wrap each one of our questions in a div and we're going to give that div an id, so then jQuery will know this is the group of items, and every item inside of here is going to be sortable, so we'll give this <div id="questions"></div>, and


document.addEventListener("turbolinks:load", function() {



By running that, we should be able to drag and drop these items, and you'll see that the mouse cursor changes now, that comes from jQuery's sortable and we can now drag these around and see them in their different places, but of course, if you refresh this, they all reorder themselves and go exactly right back to where they were. That is because this hasn't persisted to the database, what we're going to do here is we're also going to set a data-url=" <%=" here, and this is going to be basically a url to the path for sorting. We're going to need to go into our routes file, and go up to our questions resources block, and here we're going to do


Rails.application.routes.draw do 
  resources :questions do
    collection do
      patch :sort 

  #Rest of code


data-url=" <%= sort_questions_path %> "

By adding that, that will add a data attribute here that whenever you finish dragging, we can grab that url, and then send that information to the server. Here you can see data-url=" /question/sort", and we will pass in all of those id's for these questions over to the server. Now, the real trick to how all of this works is that when you create these link_to's, this is just a regular old link, and jQuery knows nothing about it, there is no magic here or anything like that but if we give these an id with a DOM id of the question, that is then going to create an id on each one of these links, those links will have an id called question_(id number), that is where the magic happens from the jQuery side, so there's a serializable method that the jQuery UI sortable comes with, and what it does is it looks through all of these items inside of your group, and it looks at the name and it splits on the underscore. It's going to create an array for us called question[]=1&question[]=2, and it will put all of these in order and send them over as an actual array that rails understands. That's where the real magic comes from, that id is parsed and everything when the serialized method is called, so I'm going to show you that, and we'll print that out in the console so you can see what it does to create those DOM id's to create the params that get sent over to rails. We need to create a callback here on the update, and this is going to be a function that will take the event and the UI and we're going to need to basically call this update function every single time tat the drop finishes. Here we can call


document.addEventListener("turbolinks:load", function() {

        update:function(e, ui) {

If we console.log that, the exact parameters that it's going to generate, so let's refresh our page and drag and drop this, and we'll see here that we get question[]=1&question[]=3, two, four, five and so on. What it's doing is it's giving us all of those items in the order that is listed here, but it's converting it to the format of params, so that rails will parse this, and then you will get a params question, and then it will be a params of all of these ids, so we can then take that params and then take those ids, loop through them, and save their position in the database and all of this will be wired up really really nicely. That's a really cool thing that comes with jQuery sortable that will take those items and serialize them for you, so if you ever need to build something like that from scratch in the future, you can use that as a trick using the DOM id and parse that, to send that stuff over to the server. Since we're using rails UJS in our JavaScript, we're going to do Rails.ajax instead of jQuery, and remember, this comes with three options that we need to set, we need to set url, type and data.


document.addEventListener("turbolinks:load", function() {

    update: function(e, ui) {
        url: $(this).data("url"),
        type: "PATCH",
        data: $(this).sortable('serialize'),


The last piece that we need is our sort action inside our controller. This, of course is going to:

def  index 
    @questions = Question.order(:position)

def sort 
    params[:question].each_with_index do |id, index|
        Question.where(id: id).update_all(position: index + 1)

head :ok

Let's try this out, we should be able to drag this one to save the very bottom, and if we see that go correctly we can go into our rails logs, we will see a patch, you see question, and we get that array of items, and then we get an update question set position where the question id is on that id, and as you might notice, I did a little bit of a weird thing here, normally you might say Question.find(id), and then update the position on that index, and that would actually issue two requests, first, it's going to load the question out of the database, and then it's going to update it, if we do the where and update all, that's actually going to do a single request for each one of those where we get only an update request to our database. If we hop back to the browser, we can refresh the page and our order is exactly the same, we can now drag and drop this to the middle, if we refresh the page, none of those have changed at all and everything is persisted and organized in the exact same way, so this is really awesome, and my favorite part of this whole solution is that sortable serialized just takes those DOM ids that rails already knows about and converts them back in a parameter format that rails can parse really easily as params question, and we don't have to do anything fancy server side like splitting a string on commas, which is kind of a common solution for somethings, and our html dictates basically how our JavaScript runs, so if you ever wanted to add maybe lessons in here, as long as your lessons had a url, data-url on it and had DOM ids for each of those items, it would be able to submit just as well, and the only thing that you would have to change is the selector, so you could add maybe lessons here, or you could change that to .sortable, and then make it apply to anything that you tagged with the class called sortable, or data-behavior="sortable" or anything you might want to do like that, so you could have data-behavior="sortable", and you could have that data attribute to any of those items in your app that you want to make sortable, so this little piece of JavaScript is kind of a little power house that you could copy and paste in other applications, and voila, you will have sortable functionality within five minutes or less if you use this sort of thing and reuse it and set up these and set up these sort methods for your models in their equivalent controllers.

That's it for this episode, I was thinking about building a Trello clone as an example where you could dive into this more in depth, maybe use a vuejs to drag and sort library as well and build more complex version of this, and if you're interested in seeing that, let me know in the comments below and we will dive into that. So, until then, I will talk to you later. Peace



Just a heads up, in your Gemfile, bootstrap 4 beta no longer uses tether for tooltips, now uses popper.js which is now a dependency of the bootstrap 4 gem


Yeah! Popper is nice to have instead. Screencast was using an old version of my Rails template which has been updated already, just hadn't done a git pull recently on that machine. Should update that in the repo.


I'd love to see you go further into this topic with Vuejs!


I'd DEFINITELY be interested in the Vuejs version - I've got an app in mind that it would be very helpful for...


This approach for updating reordered records will produce O(n) queries. If you use Postgres, it is possible to do the same operation with a single update: The only disadvantage here is that ActiveRecord can't do queries like this (correct me if I'm wrong), so it will take direct SQL execution.


+1 for VueJS as well


Here's another implementation that avoids excess queries and lets you use the standard update controller action. It uses the insert_at method that acts_as_list provides to move the item within the list:

# app/models/question.rb
def new_position=(position)

Make sure to add :new_position to permitted params in questions controller

# coffeescript
$(document).on 'turbolinks:load', ->
axis: 'y'
handle: '.handle'
update: (e, ui) ->
data = new FormData
data.append "question[new_position]", ui.item.index() + 1
url: "/questions/#{'question-id')}"
type: "PATCH"
data: data

Thank you this was an awesome tutorial, will definitely be watching more from you!! 

A few pretty basic observations/questions:

(1) When I use: 
nothing hits the server after the drag/drop is complete. 

I changed to:
and that fixed the problem. Any ideas why the former did not work and the latter did?

(2) Instead of 
//= require jquery-ui/widget
//= require jquery-ui/sortable

I needed to use
//= require jquery-ui/widgets/sortable
to avoid an error message. I suspect this has something to do with either (a) a new version of jquery-ui or (b) my lack of understanding of the asset pipeline.

(3) My implementation required me to the skip authenticity token on the sort method with:
skip_before_action :verify_authenticity_token, only: [:sort]

How does the code in this tutorial work without skipping the authenticity token verification?

Thanks, as always, for such wonderful tutorials!

Thanks Joel!

Some answers to your questions (not exactly in order):

1. Rails.ajax is the new replacement for jquery's ajax ($.ajax) now that Rails no longer comes with jQuery. Rails.ajax is also smart enough to include the authenticity token in every request which is why you don't have to do #3. 

3. For your code, you should include the authenticity token in the request instead of disabling the check as you'll open yourself up to security vulnerabilities by turning it off.

2. jQuery-ui may have moved sortable to the widgets folder since I recorded the episode. Things are always changing so you'll often run into little changes like that.
Thanks, Chris. Strangely, Rails.ajax doesn't fire an ajax request for me even though $.ajax works fine. I'm using Rails 5.1.5 and I've confirmed the rails-ujs file is included. Good to know about the authenticity token being included in every request with Rails.ajax.
hey guys thank you both, Joel your comment helped me to solve my problem. And Chris, man I love what you're doing, I started to learn Ruby and Rails some months ago and you already helped me a lot with your tutorials. What you're doing is something that money cant pay honestly. Keep going 

Thanks for the great video Chris! I'm running into a 'sortable is not a function' error that I'm hoping you can help with:
# Gemfile
gem 'jquery-ui-rails', '~> 6.0', '>= 6.0.1'
gem 'acts_as_list', '~> 0.9'

# application.js
//= require jquery
//= require rails-ujs
//= require turbolinks
//= require jquery.easy-autocomplete
//= require trix
//= require jquery-ui/widget
//= require jquery-ui/widgets/sortable
//= require_tree .

# application.html
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  <%= yield %>

# projects/index.html.erb
<div id="projects-index-body">
  <%= render @projects %>

# _project.html.erb
<div class="index-item">
  <div class="index-title">
    <%= link_to, project %>

# projects.js
document.addEventListener('turbolinks:load', function() {
  $('#projects-index-body .index-item').sortable();

The error I get is:
Uncaught TypeError: $(...).sortable is not a function
    at HTMLDocument.<anonymous> (projects.self-aa444772b5cec4b7d7c04af684612adbe666b794a474b2ed041a79415d330c43.js?body=1:2)
    at Object.Turbolinks.dispatch (turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:6)
    at e.notifyApplicationAfterPageLoad (turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:7)
    at e.pageLoaded (turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:7)
    at turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:6
(anonymous) @ projects.self-aa444772b5cec4b7d7c04af684612adbe666b794a474b2ed041a79415d330c43.js?body=1:2
Turbolinks.dispatch @ turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:6
e.notifyApplicationAfterPageLoad @ turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:7
e.pageLoaded @ turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:7
(anonymous) @ turbolinks.self-2db6ec539b9190f75e1d477b305df53d12904d5cafdd47c7ffd91ba25cbec128.js?body=1:6

Interestingly I also get a buttload of other errors like:
Uncaught TypeError: Cannot read property 'dialog' of undefined
Uncaught TypeError: Cannot read property 'bridge' of undefined
Uncaught TypeError: Cannot read property 'mouse' of undefined
Uncaught TypeError: $.widget is not a function
Uncaught TypeError: Cannot set property 'position' of undefined
Uncaught TypeError: Cannot set property 'focusable' of undefined
Uncaught TypeError: Cannot set property 'keyCode' of undefined
Uncaught TypeError: Cannot set property 'escapeSelector' of undefined
Uncaught TypeError: Cannot set property 'formResetMixin' of undefined
Uncaught TypeError: Cannot set property 'plugin' of undefined
Uncaught TypeError: Cannot set property 'safeBlur' of undefined

Which makes me think that I'm missing a require in my application.js - I just have no idea which one!

Thanks in advance for taking a look, I totally understand if this error is too specific for you to help with.

Would love to see a Trello clone!

Hey Seph! I did a whole series on it here:

Hope you enjoy it!

Hi Chris, I would need to be able to sort items in a list as you've shown in this episode, but also move items between linked lists. How would you implement this? Problem is that in this case I need to remove the item from the old list and add it to the new list, besides updating the positions, and the new positions should be correct in both lists without leaving gaps in any of them. So I guess the serialize trick and just updating positions in batch in the controller would not be enough? I also noticed that when you move an item from a list to another, jQuery UI Sortable triggers two update events, one for the source list and the other for the destination list. Thanks a lot in advance for your help!
Update: actually... it seems to work just fine. The two requests update the two lists correctly with no other changes. Thanks! :)

Another question.... how would you go about testing sorting with Capybara/system tests? Thanks


If you know you are not going to respond to our questions, why do you keep the comment session on the blog?


Hi Chris,

This is such a useful video! With a little help from your friendly neighborhood GoRails Slack channel I got it (mostly) working, but for some reason it's not serializing correctly. According to my server log, it is sending PATCH data to the database:

Started PATCH "/tasks/sort" for at 2018-10-20 11:16:06 -0700
Processing by TasksController#sort as */*
  Parameters: {"task"=>["2", "8"]}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ /Users/lizbayardelle/.rvm/gems/ruby-2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
  Task Update All (0.2ms)  UPDATE "tasks" SET "position" = 1 WHERE "tasks"."id" = ?  [["id", 2]]
  ↳ app/controllers/tasks_controller.rb:27
  Task Update All (0.1ms)  UPDATE "tasks" SET "position" = 2 WHERE "tasks"."id" = ?  [["id", 8]]
  ↳ app/controllers/tasks_controller.rb:27
Completed 200 OK in 3ms (ActiveRecord: 0.5ms)

But for some reason it always stays the original order on refresh. Any hints as to what could be going wrong?

The only difference between my app and the example app is that I had to have the .sortable act on a class (.taskWrapper not #taskWrapper) because I have the tasks in a partial that's being rendered more than one place on the page. Could that be affecting it?

Login or create an account to join the conversation.