Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit

* Set _position after updating the rank order due to a non-integer position setting

* Fix for dynamic method names in tests

* Update needs to be of row_position, not row column itself

* Use previously_changed? because this is after_save

* Adjust Appraisals, gemspec and CI matrix for Rails 5.2+ only support

* Rails 7 requires Ruby 2.7+

* Try setting position to relative rank in rank_at if needed

* Remove unnecessary additional callback

* Handle rebalance rank setting (happens before position is set)

* Don't use rank_at when rebalancing, instead, mutate the column directly


Co-authored-by: Brendon Muir <>

Git stats


Failed to load latest commit information.

ranked-model is a modern row sorting library built for Rails 4.2+. It uses ARel aggressively and is better optimized than most other libraries.

Build Status


ranked-model passes specs with Rails 4.2, 5.0, 5.1, 5.2, 6.0 and 6.1 for MySQL, Postgres, and SQLite on Ruby 2.4 through 3.0 (with exceptions, please check the CI setup for supported combinations), and jruby- where Rails supports the platform.

To install ranked-model, just add it to your Gemfile:

gem 'ranked-model'

# Or pin ranked-model to git
# gem 'ranked-model',
#   git: ''

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

Give them an order (integer column):

rails g migration AddRowOrderToDucks row_order:integer
rails db:migrate

IMPORTANT: The _order table column MUST allow null values. For the reason behind this requirement see issue#167

Put your ducks in a row:

class Duck < ActiveRecord::Base

  include RankedModel
  ranks :row_order


Order the Ducks by this order:


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 row_order_position: 0  # or 1, 2, 37. :first, :last, :up and :down are also valid

IMPORTANT: Note that you MUST append _position to the column name when setting a new position on an instance. This is a fake column that can take relative as well as absolute index-based values for position.

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];, JS can look pretty elegant:

  type: 'PUT',
  url: '/ducks',
  dataType: 'json',
  data: { duck: { row_order_position: 0 } },  // or whatever your new position is

If you need to find the rank of an item with respect to other ranked items, you can use the {column_name}_rank method on the model instance. {column_name} is your resource ranking column.

Following on from our examples above, the row_order_rank method will return the position of the duck object in the list with respect to the order defined by the row_order column.

Duck.rank(:row_order).first.row_order_rank # => 0
Duck.rank(:row_order).third.row_order_rank # => 2

Complex Use

The ranks method takes several arguments:

class Duck < ActiveRecord::Base

  include RankedModel

  ranks :row_order,                  # Name this ranker, used with rank()
    column: :sort_order              # Override the default column, which defaults to the name

  belongs_to :pond
  ranks :swimming_order,
    with_same: :pond_id              # Ducks belong_to Ponds, make the ranker scoped to one pond

  ranks :row_order,
    with_same: [:pond_id, :breed]    # Lets rank them by breed

  scope :walking, where(walking: true )
  ranks :walking_order,
    scope: :walking                  # Narrow this ranker to a scope

  belongs_to :parent, class_name: 'Duck', optional: true
  ranks :child_order,
    unless: :has_no_parent?,         # Rank only ducks that have a parent. Alternatively a Proc or lambda can be passed, e.g. proc { parent.nil? }
    with_same: :parent_id

  def has_no_parent?

When you make a query, add the rank:





While ranked-model is performant when storing data, it might cause N+1s depending on how you write your code. Consider this snippet:

ducks = Duck.all do |duck|
    position: duck.row_order_rank # This causes N+1!

Every call to duck.row_order_rank will make a call to the DB to check the rank of that particular element. If you have a long list of elements this might cause issues to your DB.

In order to avoid that, you can use the rank(:your_rank) scope and some Ruby code to get the element's position:

ducks = Duck.rank(:row_order).all do |duck, index|
    position: index

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

class Car < Vehicle

class Truck < Vehicle

car = Car.create!
truck = Truck.create!

=> 0
=> 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'

class Car < Vehicle

class Truck < Vehicle

car = Car.create!
truck = Truck.create!

=> 0
=> 4194304

Migrations for existing data

If you use ranked_model with existing data, the following migration (for Rails 6) can be a starting point. Make sure to declare include RankedModel and ranks :row_order in your Duck before running the migration.

rails g migration AddRowOrderToDucks row_order:integer

Then, adjust the migration:

# e.g. file db/migrate/20200325095038_add_row_order_to_ducks.rb
class AddRowOrderToDucks < ActiveRecord::Migration[6.0]
  def change
    add_column :ducks, :row_order, :integer

    # Newest Duck shall rank "highest"" (be last).
    Duck.update_all('row_order = EXTRACT(EPOCH FROM created_at)')

    # Alternatively, implement any other sorting default
    # Duck.order(created_at: :desc).each do |duck|
    #   duck.update!(row_order: duck.created_at.to_i + duck.age / 2)
    # end


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 -2147483648 and 2147483647 (the INT 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.


Fork, clone, write a test, write some code, commit, push, send a pull request. Github FTW!

The code is published under the MIT License.

The specs can be run with sqlite, postgres, and mysql:

appraisal install
DB=postgresql bundle exec appraisal rake

If no DB is specified (sqlite, mysql, or postgresql), the tests run against sqlite.

RankedModel is mostly the handiwork of Matthew Beale:

A hearty thanks to these contributors: