Ranked_Model: A Rails Gem for Multiple Data Sets
Published: 2018-10-30 23:03:22

---
title: Ranked_Model: A Rails Gem for Multiple Data Sets
published: true
description: Comparing ranked_model and acts_as_list gems
tags: 
canonical_url: https://blog.planetargon.com/entries/ranked_model-a-rails-gem-for-multiple-data-sets
---
![Ranked_Model](https://thepracticaldev.s3.amazonaws.com/i/edb6od4t39bjrzraj5fa.png)
Recently we ran into an issue on a client Rails application where we wanted to customize the order in which certain items would appear on various pages. The admin users would log in to a CMS built with [`active_admin`](https://github.com/activeadmin/activeadmin), create a widget with information about a company, and then either manually set the position in the list while creating or editing the widget, or use a 'move up and down' feature on the index page. 

Originally, we utilized the [`acts_as_list`](https://github.com/swanandp/acts_as_list) gem to implement this feature as it allowed us to quickly and painlessly add all the functionality we needed.

I ran into an issue, though, when our client requested that each widget have two separate rankings, depending on which page the user was visiting. If the user was on the Foo product page, the widget might appear at position 2, but on the Bar page the widget would render in position 3. 

My initial instinct was to create a new `position` attribute on the widget model and pass in both the new attribute and the existing position column into the `acts_as_list` method. However, I hit a roadblock since `acts_as_list` is not capable of handling two different attributes on a single model.

This was a time sensitive request and as I dove into the possible solutions of using associations and join tables to add this feature with `acts_as_list`, I couldn't help but feel that there should be a quicker solution that could handle having two different ranking systems for a single model.

My search led me to discover [`ranked_model`](https://github.com/mixonic/ranked-model), a gem with a similar goal to `acts_as_list` but with a different implementation and functionality, most of which fit our criteria with much more ease than the `acts_as_list` approach.

After installing the gem and including RankedModel in the class file, you can simply declare which attributes will be handled by `ranked_model` with the following lines:

`ranks :foo`
`ranks :bar`

This would work fine on a greenfield project or a new attribute but I was adding this to an attribute that already had position-oriented ranking applied. Both gems use integers as their data types, but for the most part, the similarities end there.

`acts_as_list` uses sequential numbers in its assigned attribute, incrementing and decrementing multiple objects as necessary with each change in the order. For example by taking a list of widgets with positions of A: 1, B: 2, C: 3, & D: 4, then moving D to the first position, D would become 1 but B, C, and A would all be incremented to fully reorder the list. There are more methods to run each time but it’s simple to glance at any given object and understand its exact position.

`Ranked_model` on the other hand, utilizes relatively large numbers, both positive and negative, to more quickly and efficiently order objects. Creating the same 4 objects with ranked_model looks far different, that acts_as_list, being more like this:

`A: -2342567, B: -1437689, C: 1289078, D: 2165876`

Since ranked_model generally deals with larger integers, it can cause more difficulty in development and debugging, particularly in the console. Once a sizable number of objects is reached, finding the relative place in the ranking from only its position attribute would require  notable attention to detail. However, calling a method to actually move these positions is simple enough. 

To mimic the same widget repositioning as the acts_as_list example, moving D to the first position, the code would require a call of:

```ruby
D.update_attribute :foo_ranking_position, :first
```

which would result in something like:

`D: -2465876, A: -2342567, B: -1437689, C: 1289078`

So you can see, using these large integers allowed us to reorder D to the front and only modify a single object! This becomes especially valuable in situations with large datasets. As mentioned earlier, ranked_model also allows us to call rankings on multiple attributes, so using:

```ruby
D.update_attributes {
  foo_ranking_position: :first,
  bar_ranking_position: :last
}
```

would be completely acceptable.

Migrating from acts_as_list to ranked_model did take a little bit of work, though. Since we already had an ordered attribute from acts_as_list, attempting to just write in and start using ranked_model on it outright led to some strange interactions and incorrect sorting. My solution was to add the following to a database migration:

```ruby
reversible do |direction|
  direction.up do
    Widget.order(:foo_position).each do |widget|
      widget.update_attribute :foo_position, :last
    end
    Widget.rank(:foo_position).each do |widget|
      widget.update_attribute :bar_position, :last
    end
  end
end
```

This is probably a little overkill and almost certainly could be refactored to look more elegant or efficient (and also should probably be a rake task) but it worked with our need for a quick turnaround. It's essentially going through our original order from `acts_as_list` and telling `ranked_model` to order it at the end of the list, making sure all positions now have a ranked_model compatible position.


After using some of the syntax particular to the gem, in order to modify the methods and controllers that were updating position in the class file, ranked_model proved to be an efficient way to sort and rank multiple datasets for Rails.