Project

rankle

0.0
No commit activity in last 3 years
No release in over 3 years
There's a lot of open issues
Rankle provides multi-resource ranking.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

Rankle

Rankle provides multi-resource ranking. It uses a separate join table rather than a resource specific position column.

Installation

Add this line to your application's Gemfile:

gem 'rankle'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rankle

Getting Started

Before you can use Rankle, you'll need to generate the index table. Rankle provides a generator to assist with this:

$ rails g rankle:install

The generator only creates the migration file. You'll still need to run the migration:

$ rake db:migrate

Default Behavior

Simply including Rankle is intended to be ineffectual:

class Fruit < ActiveRecord::Base
end

Fruit.all.to_a == Fruit.ranked.to_a # true

However, new records will respond to position:

apple  = Fruit.create!
orange = Fruit.create!

apple.position  # 0
orange.position # 1

The ranked method provides an ordered ActiveRecord::Relation:

Fruit.create! name: 'apple'
Fruit.create! name: 'orange'

Fruit.ranked.map(&:name) # ['apple', 'orange']

Simple Usage

You can assign an explicit ranking in several ways. The position attribute can be set directly:

apple.update_attribute :position, 1

apple.position  # 1
orange.position # 0

Fruit.ranked.map(&:name) # ['orange', 'apple']

When called with an integer, the rank method will assign the position:

apple.rank 0

apple.position  # 0
orange.position # 1

Fruit.ranked.map(&:name) # ['apple', 'orange']

You can declare a proc to maintain a functional position ranking:

class Fruit < ActiveRecord::Base
  ranks ->(a, b) { a.name < b.name }
end

Fruit.create! name: 'apple'
Fruit.create! name: 'orange'
Fruit.create! name: 'banana'

Fruit.ranked.map(&:name) # ['apple', 'banana', 'orange']

Named Ranking

Passing a symbol to the rank method with a position will update the position to that named rank:

apple  = Fruit.create! name: 'apple'
orange = Fruit.create! name: 'orange'

apple.rank  :reverse, 1
orange.rank :reverse, 0

apple.position  # 0
orange.position # 1

apple.position  :reverse # 1
orange.position :reverse # 0

Fruit.ranked.map(&:name)           # ['apple', 'orange']
Fruit.ranked(:reverse).map(&:name) # ['orange', 'apple']

Since positions are not stored with an absolute value, the available positions increases by 1 with each call to the rank method:

apple  = Fruit.create! name: 'apple'
banana = Fruit.create! name: 'banana'
orange = Fruit.create! name: 'orange'

apple.rank  :reverse, 2 # [apple]
banana.rank :reverse, 1 # [banana, apple]
orange.rank :reverse, 0 # [orange, banana, apple]

apple.position  # 0
banana.position # 1
orange.position # 2

apple.position  :reverse # 1
banana.position :reverse # 2
orange.position :reverse # 0

Fruit.ranked.map(&:name)           # ['apple', 'banana', 'orange']
Fruit.ranked(:reverse).map(&:name) # ['orange', 'apple', 'banana']

You can bypass this issue by registering the ranking on the class:

class Fruit < ActiveRecord::Base
  ranks :reverse
end

apple  = Fruit.create! name: 'apple'
banana = Fruit.create! name: 'banana'
orange = Fruit.create! name: 'orange'

apple.position  # 0
banana.position # 1
orange.position # 2

apple.position  :reverse # 0
banana.position :reverse # 1
orange.position :reverse # 2

apple.rank  :reverse, 2 # [banana, orange, apple]
banana.rank :reverse, 1 # [banana, orange, apple]
orange.rank :reverse, 0 # [orange, banana, apple]

apple.position  # 0
banana.position # 1
orange.position # 2

apple.position  :reverse # 2
banana.position :reverse # 1
orange.position :reverse # 0

Fruit.ranked.map(&:name)           # ['apple', 'banana', 'orange']
Fruit.ranked(:reverse).map(&:name) # ['orange', 'banana', 'apple']

Multiple Resources

Passing a symbol to the rank method with a position will update the position to that named rank:

class Fruit < ActiveRecord::Base
end

class Vegetable < ActiveRecord::Base
end

apple  = Fruit.create!     name: 'apple'
carrot = Vegetable.create! name: 'carrot'

apple.rank  :produce, 0
carrot.rank :produce, 1

apple.position  # 0
carrot.position # 0

apple.position  :produce # 0
carrot.position :produce # 1

Fruit.ranked.map(&:name)               # ['apple']
Vegetable.ranked.map(&:name)           # ['carrot']

Fruit.ranked(:produce).map(&:name)     # ['apple']
Vegetable.ranked(:produce).map(&:name) # ['carrot']

Notice that the ranked method can't increase the scope of your query. Multi-resource relations can be accessed through the Rankle class which serves as the global ranking scope.

Rankle.ranked(:produce).map(&:name)    # ['apple', 'carrot']

Passing a symbol to the ranks method will register the resource to that ranking. This will automatically assign the default position to new records within the shared ranking:

class Fruit
  ranks :produce
end

Class Vegetable
  ranks :produce
end

apple  = Fruit.create!     name: 'apple'
carrot = Vegetable.create! name: 'vegetable'

apple.position  # 0
carrot.position # 0

apple.position  :produce # 0
carrot.position :produce # 1

Scoped Ranking

ActiveRecord scopes work in conjunction to further restrict the ranking:

class Fruit
  scope :berries, -> { where "name LIKE ?", '%berry' }
end

Fruit.create! name: 'apple'
Fruit.create! name: 'apricot'
Fruit.create! name: 'banana'
Fruit.create! name: 'bilberry'
Fruit.create! name: 'blackberry'
Fruit.create! name: 'blackcurrant'
Fruit.create! name: 'blueberry'
Fruit.create! name: 'boysenberry'
Fruit.create! name: 'cantaloupe'

Fruit.ranked.map(&:name)         # ['apple', 'apricot', 'banana', 'bilberry', 'blackberry', 'blackcurrant', 'blueberry', 'boysenberry', 'cantaloupe']
Fruit.berries.ranked.map(&:name) # ['bilberry', 'blackberry', 'blueberry', 'boysenberry']

Contributing

  1. Fork it ( https://github.com/[my-github-username]/rankle/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request