Announcing a new positioning gem: positioning

I’m the maintainer of the acts_as_list and ranked-model gems and recently I’ve written a new positioning gem of my own. It’s named unimaginatively: positioning.

It takes the best of both of the aforementioned gems and adds an advisory lock to guarantee sequential list operations:

  • Doesn’t pollute the model namespace (inspired by ranked-model)
  • Uses sequential integer position values like acts_as_list.
  • Honours uniqueness constraints when expanding and contracting space in the list.
  • Promotes the use of relative positioning over explicit positioning (e.g. before: other_list_member)
  • Does its best to prevent gaps in the position sequence via clamping and an advisory lock.
  • Allows more than one list to exist per model (like ranked-model)
  • Scope by belongs_to relationship, model column, a combination of the two, or not at all!
  • Automatically handles moving list items between scopes (like acts_as_list and ranked-model).

I’m using it in production and encourage others to take a look and provide feedback.

I’ll still be maintaining acts_as_list and ranked-model so don’t fret :smiley:

9 Likes

This looks very promising and has handy create/update helpers. Project looks neat, well done. I’m currently an ‘Ancestry’ gem user, but will definitely look into the ‘Positioning’ gem.

I like the idea how to easily assign positions using your gem:

item.create position: :last
item.create position: {before: 22}
item.update position: {after: other_item}
etc.

Thanks @Frank1, I use ancestry too! :slight_smile: It’s a very useful gem. I’ve actually used it in conjunction with acts_as_list and ranked-model in different projects to have an ordered tree. It’s easy to scope to the ancestry column. I haven’t tried that with my gem but I assume it’d work fine. Let me know how it goes :slight_smile:

Is it possible to add positioning conditionally? I am trying this gem for a single model and need to set position for a particular field - conditionally something like this

class TestModel < ApplicationRecord
  positioned on:  :position, if proc {|t| t.another_field == 'something'}
end

Every record will belong to a list with positioning. You can use a boolean scope and just disregard the ordering in the false scope. It’ll still happen though:

positioned on: :boolean_column

I haven’t designed for a condition evaluated in Ruby however since the SQL statements used to rearrange the list wouldn’t work in that case.

1 Like

Yeah, that make sense. I think I can make a workaround by skipping the order for a particular condition. Thanks for the explanation!

This looks great. Does it work with polymorphic relations? I’m just playing around with it now but can’t figure it out.

How could I create a position on Task for both Job and Project?

class Project
  has_many :tasks
end

class Job
  has_many :tasks
end

class Task
  belongs_to :taskable, polymorphic: true
end

Do you want to have a seperate list for Job Tasks and another for Project Tasks? If so I think you could:

positioned on: :taskable_type

Let me know if I’ve missed the point :slight_smile:

1 Like

Ahhhh, you gave me the aha moment. So it’s scoped to a column or columns that all match. Seems completely obvious now.

This was what I was looking for.

positioned on: [:taskable_type, :taskable_id]

Thanks fellow kiwi!

You’re most welcome! :slight_smile: Best country in the world right?! :wink:

We actually sniff for belongs_to reflections and so if you pass positioned on: :taskable we’d translate that to taskable_id which would be incorrect here. I’ll do an update to cater for polymorphic belongs_to. Once I’ve released the change you should be able to just do positioned on: :taskable.

1 Like

Released as 0.4.3 Enjoy! :smiley:

1 Like