Skip to content

Repository files navigation

Orderable

Test CoverageRuby Style GuideTest and Coverage Workflow

A gem that makes it easy to change the default order of PostgreSQL database rows by the addition of a modifiable integer column.

Features

  • Automatic reordering on Active Record CRUD operations
  • Configurable record positioning
  • Positioning field migration generator

Table of contents

Basic usage

Let's consider the AR image model that implements the orderable method. Its positioning field name is set as position and it has only 2 properties - id and label. Images table content is presented below.

idnameposition
1"A"1
2"B"2
3"C"0
classImage < ApplicationRecordorderable:positionendImage.pluck(:label,:position)# => [["A", 1], ["B", 2], ["C", 0]]Image.ordered.pluck(:label)# => ["C", "A", "B"]# on createimage=Image.create(label: "D")image.position# => 3Image.ordered.pluck(:label)# => ["C", "A", "B", "D"]# on updateimage.update(position: 2)Image.ordered.pluck(:label)# => ["C", "A", "D", "B"]# on destroyimage.destroy()Image.ordered.pluck(:label,:position)# => [["C", 0], ["A", 1], ["B", 2]]

Notice that you can pass direction :asc/:desc to ordered scope as parameter:

Image.pluck(:label,:position)# => [["A", 1], ["B", 2], ["C", 0]]# :asc by defaultImage.ordered.pluck(:label)# => ["C", "A", "B"]Image.ordered(:asc).pluck(:label)# => ["C", "A", "B"]# :descImage.ordered(:desc).pluck(:label)# => ["B", "A", "C"]

Installation

Install the orderable gem from Rubygems:

$ gem install orderable

or

Add this line to your application's Gemfile:

gem'orderable','VERSION'

Then run:

$ bundle install

Generate migration

In the Rails project directory, type the command:

$ rails generate orderable:migration table_name:field_name scopes
  • table_name: name of the table for which positioning field migration will be generated
  • field_name: name of the new column that will be added and used for positioning
  • scopes: additional scopes separated with spaces used to put unique index on the whole group

Generated migration will be placed in your default migrations directory db/migrate .

Example: Let's consider an Image model with foreign keys for Owner and Project. The following command should be run:

$ rails generate orderable:migration Image:position owner_id project_id

This will generate migration adding position column to images table with unique index on position, owner_id and project_id.

The next step is to migrate database with:

$ rails db:migrate

NoteCurrently, the default Rails schema does not support deferrable unique index. If you want to ensure uniqueness on orderable field after rebuilding the database from schema, you need to change it to structure schema. For more information on how to do it, see the link.

Include orderable in AR model

To use orderable on added column you need to specify it in model by calling orderable method:

orderable:orderable_field_name

Optional named arguments:

AttributeValueDefaultDescription
scopearray of symbols[]scope same as in unique index (uniqueness of this fields combination would be ensured)
auto_setbooleantrueif true and positioning field value is not specified, it inserts a new record on the bottom for decremental sequence or on the top for incremental sequence on create
sequence:incremental or :decremental:incrementalvalue used to determine positioning sequence
validatebooleantrueif true, it validates numericality of positioning field value, as well as being in range <0, M>, where M stands for the biggest positioning field value
frominteger0base value from which sequence starts

Usage Examples

Model with a scope

Let's say a user has few cover and profile photos. Using orderable with scope will allow user to customize their order separately.

classPhoto < ActiveRecord::Baseorderable:position,scope: :typescope:profile,->{where(type: 'profile')}scope:cover,->{where(type: 'cover')}endPhoto.pluck(:label,:position,:type)# => [["A", 0, "profile"], ["E", 1, "cover"], ["C", 2, "profile"], ["B", 1, "profile"], ["D", 0, "cover"]]Photo.ordered.pluck(:label)# => ["A", "B", "C", "D", "E"]Photo.profile.ordered.pluck(:label,:position)# => [["A", 0], ["B", 1], ["C", 2]]Photo.cover.ordered.pluck(:label,:position)# => [["D", 0], ["E", 1]]# on createphoto=Photo.create(label: "F",type: "profile")photo.position# => 3Photo.profile.ordered.pluck(:label,:position)# => [["A", 0], ["B", 1], ["C", 2], ["F", 3]]Photo.cover.ordered.pluck(:label,:position)# => [["D", 0], ["E", 1]]# on updatephoto.update(type: "cover")photo.position# => 2Photo.profile.ordered.pluck(:label,:position)# => [["A", 0], ["B", 1], ["C", 2]]Photo.cover.ordered.pluck(:label,:position)# => [["D", 0], ["E", 1], ["F", 2]]photo.update(position: 1)Photo.profile.ordered.pluck(:label,:position)# => [["A", 0], ["B", 1], ["C", 2]]Photo.cover.ordered.pluck(:label,:position)# => [["D", 0], ["F", 1], ["E", 2]]# on destroyphoto.destroy()Photo.profile.ordered.pluck(:label,:position)# => [["A", 0], ["B", 1], ["C", 2]]Photo.cover.ordered.pluck(:label,:position)# => [["D", 0], ["E", 1]]

Auto set

classImage < ActiveRecord::Baseorderable:position,auto_set: true# by defaultendimage=Image.create(label: "A")# => OKimage.position# => 0image=Image.create(label: "B")# => OKImage.ordered.pluck(:label,:position)# => [["A", 0], ["B", 1]]classPost < ActiveRecord::Baseorderable:position,auto_set: falseendPost.create(title: "A")# => validation error (position is not specified)Post.create(title: "A",position: 0)# => OKPost.create(title: "B",position: 0)# => OKPost.ordered.pluck(:title,:position)# => [["B", 0], ["A", 1]]

Disabling validation

classImage < ActiveRecord::Baseorderable:position,validation: true# by defaultendImage.count# => 0Image.create(label: "A",position: -1)# => validation error (cannot be negative)Image.create(label: "A",position: 1)# => validation error (no image with position 0)classPost < ActiveRecord::Baseorderable:position,validation: falseendPost.count# => 0Post.create(title: "A title",position: -1)# => OK

Setting from value

classImage < ActiveRecord::Baseorderable:position,from: 10endImage.create(label: "A")Image.create(label: "B")Image.ordered.pluck(:label,:position)# => [["A", 10], ["B", 11]]

Decremental sequence

classImage < ActiveRecord::Baseorderable:position,from: 10,sequence: :decrementalendImage.create(label: "A")Image.create(label: "B")Image.create(label: "C")Image.ordered.pluck(:label,:position)# => [["C", 8], ["B", 9], ["A", 10]]

License

The gem is available as open source under the terms of the MIT License.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages