Positionable

<img src=“https://secure.travis-ci.org/pguegan/positionable.png” />

Positionable is a library which provides contiguous positionning capabilities to your ActiveRecord models.

For more functionalities, you could also have a look at acts_as_list.

Installation

Edit your Gemfile, and simply add the following line:

gem 'positionable'

Getting Started

Let’s say you want to make the model Item positionable.

Create a migration

First, create a migration to add the column position to the table items:

rails generate migration add_position_to_items position:integer

Then, run the migration:

rake db:migrate

Setup your model

Simply add the is_positionable method in your ActiveRecord model:

class Item < ActiveRecord::Base
  is_positionable
end

Grouping records

Maybe your items are grouped (typically with a belongs_to association). In this case, you’ll want to restrict the position in each group by declaring the :scope option:

class Item < ActiveRecord::Base
  belongs_to :folder
  is_positionable :scope => :folder
  attr_accessible :folder_id, :position
end

Note that it is the model responsibility to give the white-list of attributes that can be updated via mass-assignement. In this case, you must add the position attribute in the attr_accessible clause.

Start position

By default, position starts by zero. But you may want to change this at the model level, for instance by starting at one (which seems more natural for some people):

class Item < ActiveRecord::Base
  is_positionable :start => 1
end

Ordering

When a new record is created, it is inserted by default at the last (highest) position of its group. Thus, when record are listed, the newly created record will appear at the bottom.

It is possible to change this behaviour by setting the order option as follows:

class Item < ActiveRecord::Base
  is_positionable :order => :desc
end

This way, records are always listed by descending positions order. Record that have the highest position will appears at the top.

Caution! The semantic of next or previous methods remains unchanged. More precisely, even if the highest position is the position of the first returned record, it is still considered as the last one (i.e.: Item.first.last? returns true). I know, this is odd. The semantic of these methods will certainly change in a further version.

Mixing options

Obviously, these options are not exclusive. You are free to mix them like, for example:

class Item < ActiveRecord::Base
  belongs_to :folder
  is_positionable :scope => :folder, :order => :desc, :start => 1
end

Usage

Querying

To get the previous or next sibling items:

previous = item.previous
all_previous = item.all_previous
next = item.next
all_next = item.all_next

Both first and last items can be caracterized:

item.first?   # True if item.previous is nil
item.last?    # True if item.next is nil

Given a positionable item, its position is always included in a range which can be determined as follows:

item.range

If this item is aimed at being moved to another different scope, then you can pass this new scope as a parameter:

item.range(folder)

Moving

Rather than directly assign position attribute, you can move your items with these provided methods:

item = Item.create(...)           # The newly created item is put at the last position (by default).
item.up!                          # Item's position is swaped with the previous item.
item.down!                        # Item's position is swaped with the next item.
item.move_to new_position         # Moves this record to the given position, and updates sibling items positions accordingly.

Yet, it is possible to update the item’s position via mass-assignement:

item.update_attributes { :position => new_position, ... }

This will trigger some ActiveRecord callbacks in order to maintain positions contiguity across all other concerned items, even if the item is moved from a scope to another.