Commit 07bc6987 authored by Markus Koller's avatar Markus Koller

Add RelativePositioning.move_nulls_to_start helper

This mirrors the existing `move_nulls_to_end` and can be used to
backfill existing records.

The records are positioned at the start before any other records that
already have a position set, while keeping the sequential order that
the records are passed in.

This also switches from `save` to `update_column` to avoid unnecessary
queries for things like uniqueness validation.
parent a0adf4a2
......@@ -32,17 +32,32 @@ module RelativePositioning
class_methods do
def move_nulls_to_end(objects)
objects = objects.reject(&:relative_position)
return if objects.empty?
max_relative_position = objects.first.max_relative_position
self.transaction do
max_relative_position = objects.first.max_relative_position
objects.each do |object|
relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION)
object.relative_position = relative_position
object.update_column(:relative_position, relative_position)
max_relative_position = relative_position
object.save(touch: false)
end
end
end
def move_nulls_to_start(objects)
objects = objects.reject(&:relative_position)
return if objects.empty?
self.transaction do
min_relative_position = objects.first.min_relative_position
objects.reverse_each do |object|
relative_position = position_between(MIN_POSITION, min_relative_position || START_POSITION)
object.update_column(:relative_position, relative_position)
min_relative_position = relative_position
end
end
end
......
# frozen_string_literal: true
RSpec.shared_examples 'a class that supports relative positioning' do
let(:item1) { create(factory, default_params) }
let(:item2) { create(factory, default_params) }
let(:new_item) { create(factory, default_params) }
let(:item1) { create_item }
let(:item2) { create_item }
let(:new_item) { create_item }
def create_item(params)
def create_item(params = {})
create(factory, params.merge(default_params))
end
......@@ -16,21 +16,30 @@ RSpec.shared_examples 'a class that supports relative positioning' do
end
describe '.move_nulls_to_end' do
let(:item3) { create_item }
it 'moves items with null relative_position to the end' do
item1.update!(relative_position: nil)
item1.update!(relative_position: 1000)
item2.update!(relative_position: nil)
item3.update!(relative_position: nil)
described_class.move_nulls_to_end([item1, item2])
items = [item1, item2, item3]
described_class.move_nulls_to_end(items)
items.map(&:reload)
expect(item2.prev_relative_position).to eq item1.relative_position
expect(item1.prev_relative_position).to eq nil
expect(item2.next_relative_position).to eq nil
expect(items.sort_by(&:relative_position)).to eq(items)
expect(item1.relative_position).to be(1000)
expect(item1.prev_relative_position).to be_nil
expect(item1.next_relative_position).to eq(item2.relative_position)
expect(item2.next_relative_position).to eq(item3.relative_position)
expect(item3.next_relative_position).to be_nil
end
it 'moves the item near the start position when there are no existing positions' do
item1.update!(relative_position: nil)
described_class.move_nulls_to_end([item1])
item1.reload
expect(item1.relative_position).to eq(described_class::START_POSITION + described_class::IDEAL_DISTANCE)
end
......@@ -38,9 +47,49 @@ RSpec.shared_examples 'a class that supports relative positioning' do
it 'does not perform any moves if all items have their relative_position set' do
item1.update!(relative_position: 1)
expect(item1).not_to receive(:save)
described_class.move_nulls_to_end([item1])
item1.reload
expect(item1.relative_position).to be(1)
end
end
describe '.move_nulls_to_start' do
let(:item3) { create_item }
it 'moves items with null relative_position to the start' do
item1.update!(relative_position: nil)
item2.update!(relative_position: nil)
item3.update!(relative_position: 1000)
items = [item1, item2, item3]
described_class.move_nulls_to_start(items)
items.map(&:reload)
expect(items.sort_by(&:relative_position)).to eq(items)
expect(item1.prev_relative_position).to eq nil
expect(item1.next_relative_position).to eq item2.relative_position
expect(item2.next_relative_position).to eq item3.relative_position
expect(item3.next_relative_position).to eq nil
expect(item3.relative_position).to be(1000)
end
it 'moves the item near the start position when there are no existing positions' do
item1.update!(relative_position: nil)
described_class.move_nulls_to_start([item1])
item1.reload
expect(item1.relative_position).to eq(described_class::START_POSITION - described_class::IDEAL_DISTANCE)
end
it 'does not perform any moves if all items have their relative_position set' do
item1.update!(relative_position: 1)
described_class.move_nulls_to_start([item1])
item1.reload
expect(item1.relative_position).to be(1)
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment