Resort - positionless sorting for your Rails 3 models

Lately we have been working on some drag & drop interfaces for some client work that needed model sorting. As you might know, on the front-end layer there are plenty of solutions for that (being jQuery draggable/sortable a really good one). But what to use on a model level?

We first thought of some acts_as_list-ish kind of plugins, and after thoroughly reviewing them all, we came up with an observation.

Most of sorting plugins work with an absolute position attribute that sets the weight of a given element within a tree. This field has no semantic sense, since "84" by itself gives you absolutely no information about an element's position or its relations with other elements of the tree. After you just read this, just like us, you won't be able to sleep. Think about it. All these arbitrary numbers in your database columns. Flowers aren't pretty anymore. Life sucks.

Does it?

Of course it doesn't! Computer science brings us beautiful data structures to satiate our need for beauty. Look at the structure of a linked list for example, and you will think: enough :order => "position" already! We don't need an arbitrary position field. We just need every model to reference a next and a previous. Yeah, it smells like those old C tutorials, but it's worth it :)

So, unlike most Rails sorting plugins (actsaslist, etc), Resort is based on linked lists rather than absolute position fields.

TL;DR - How can I use it?

$ gem install resort

Or in your Gemfile:

gem 'resort'  

As you just learned, you must add two fields (next_id and first) to your model's table, let's say, Product:

# db/migrate/283792358_add_resort_fields_to_products.rb
class AddResortFieldsToProducts < ActiveRecord::Migration  
  def self.up
    add_column :products, :next_id, :integer
    add_column :products, :first,   :boolean
    add_index :products, :next_id
    add_index :products, :first
  end

  def self.down
    remove_column :products, :next_id
    remove_column :products, :first
  end
end  

Then in your Product model:

# app/models/product.rb
class Product < ActiveRecord::Base  
  resort!
end  

By default, Resort will treat all products as a single big tree. But what if you have different ProductLines, and want to sort products separately withi each product line? As a sortable product, you have to tell Resort who you siblings are. To do that, just override the siblings method:

# app/models/product.rb
class Product < ActiveRecord::Base  
  resort!

  def siblings
    # Tree contains only products from my own product line
    self.product_line.products
  end
end  

Now your products are enhanced with two methods doing the following:

  • first? — Returns true if the element is the first of the tree.
  • append_to(other_element) — Appends the element after another element.

And the class Product has a new scope named ordered that returns th products in order. Easy huh?

Learn more

To learn more, you can check out the documentation.
Also, if you want to get involved, just check out the source code at Github

UPDATE: We did some benchmarks comparing the performance of Resort against RankedModel and ActsAsList.