ranked-model is a modern row sorting library built for Rails 3 & 4. It uses ARel aggressively and is better optimized than most other libraries.
Installation
ranked-model passes specs with Rails 3.1, 3.2, 4.0, and 4.1-beta for MySQL, Postgres, and SQLite on Ruby 1.9.2, 1.9.3, 2.0, 2.1, jruby-19mode, and rubinius where Rails supports the platform. This is with the exception of Postgres before Rails 4.0 on all platforms, which is unsupported (I'd gladly accept a PR to fix this).
TL;DR, if you are using Rails 4 and up you are 100% good to go. Before Rails 4, be wary of Postgres.
To install ranked-model, just add it to your Gemfile
:
gem 'ranked-model'
Then use bundle install
to update your Gemfile.lock
.
Simple Use
Use of ranked-model is straight ahead. Get some ducks:
class Duck < ActiveRecord::Base
end
Put your ducks in a row:
class Duck < ActiveRecord::Base
include RankedModel
ranks :row_order
end
This simple example assumes an integer column called row_order
. To order Ducks by this order:
Duck.rank(:row_order).all
The ranking integers stored in the row_order
column will be big and spaced apart. When you
implement a sorting UI, just update the resource by appending the column name with _position
and indicating the desired position:
@duck.update_attribute :row_order_position, 0
Position numbers begin at zero. A position number greater than the number of records acts the
same as :last. :up and :down move the record up/down the ladder by one step.
So using a normal json controller where @duck.attributes = params[:duck]; @duck.save
, JS can
look pretty elegant:
$.ajax({
type: 'PUT',
url: '/ducks',
dataType: 'json',
data: { duck: { row_order_position: 0 } },
});
Complex Use
The ranks
method takes serveral arguments:
class Duck < ActiveRecord::Base
include RankedModel
ranks :row_order,
:column => :sort_order
belongs_to :pond
ranks :swimming_order,
:with_same => :pond_id
scope :walking, where(:walking => true )
ranks :walking_order,
:scope => :walking
end
When you make a query, add the rank:
Duck.rank(:row_order)
Pond.first.ducks.rank(:swimming_order)
Duck.walking.rank(:walking)
Single Table Inheritance (STI)
ranked-model scopes your records' positions based on the class name of the object. If you have
a STI type
column set in your model, ranked-model will reference that class for positioning.
Consider this example:
class Vehicle < ActiveRecord::Base
ranks :row_order
end
class Car < Vehicle
end
class Truck < Vehicle
end
car = Car.create!
truck = Truck.create!
car.row_order
=> 0
truck.row_order
=> 0
In this example, the row_order
for both car
and truck
will be set to 0
because they have
different class names (Car
and Truck
, respectively).
If you would like for both car
and truck
to be ranked together based on the base Vehicle
class instead, use the class_name
option:
class Vehicle < ActiveRecord::Base
ranks :row_order, class_name: 'Vehicle'
end
class Car < Vehicle
end
class Truck < Vehicle
end
car = Car.create!
truck = Truck.create!
car.row_order
=> 0
truck.row_order
=> 4194304
Internals
This library is written using ARel from the ground-up. This leaves the code much cleaner
than many implementations. ranked-model is also optimized to write to the database as little
as possible: ranks are stored as a number between -8388607 and 8388607 (the MEDIUMINT range in MySQL).
When an item is given a new position, it assigns itself a rank number between two neighbors.
This allows several movements of items before no digits are available between two neighbors. When
this occurs, ranked-model will try to shift other records out of the way. If items can't be easily
shifted anymore, it will rebalance the distribution of rank numbers across all members
of the ranked group.
Contributing
Fork, clone, write a test, write some code, commit, push, send a pull request. Github FTW!
The specs can be run with sqlite, postgres, and mysql:
DB=postgres bundle exec rake
Is no DB is specified, the tests run against sqlite.
RankedModel is mostly the handiwork of Matthew Beale:
A hearty thanks to these contributors: