Skip to content

Commit

Permalink
More proactive rebalancing to ensure there's always a gap for insertions
Browse files Browse the repository at this point in the history
  • Loading branch information
dmarkow committed Jan 21, 2020
1 parent 89c49aa commit 521e37f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 8 deletions.
33 changes: 25 additions & 8 deletions lib/ecto_ranked.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,40 @@ defmodule EctoRanked do
case position do
pos when pos in ["first", :first] ->
first = get_current_first(cs, options)
rank_between(cs, options, @min, first || @max)

if first do
rank_between(cs, options, pos, @min, first || @max)
else
update_index_from_position(cs, options, :middle)
end

pos when pos in ["middle", :middle] ->
rank_between(cs, options, pos, @min, @max)

pos when pos in ["last", :last] ->
last = get_current_last(cs, options)
rank_between(cs, options, last || @min, @max)

if last do
rank_between(cs, options, pos, last || @min, @max)
else
update_index_from_position(cs, options, :middle)
end

pos when pos in ["up", :up] ->
case find_prev_two(cs, options) do
nil -> cs
{min, max} -> rank_between(cs, options, min, max)
{min, max} -> rank_between(cs, options, pos, min, max)
end

pos when pos in ["down", :down] ->
case find_next_two(cs, options) do
nil -> cs
{min, max} -> rank_between(cs, options, min, max)
{min, max} -> rank_between(cs, options, pos, min, max)
end

number when is_integer(number) ->
{min, max} = find_neighbors(cs, options, number)
rank_between(cs, options, min, max)
rank_between(cs, options, number, min, max)

nil ->
if get_field(cs, options.rank_field) &&
Expand All @@ -87,9 +100,13 @@ defmodule EctoRanked do
get_change(cs, scope_field)
end

defp rank_between(cs, options, min, max) do
rank = EctoRanked.Utils.ceiling((max - min) / 2) + min
rank_at(cs, options, rank)
defp rank_between(cs, options, position, min, max) do
if max - min <= 1 do
cs |> rebalance_ranks(options) |> update_index_from_position(options, position)
else
rank = EctoRanked.Utils.ceiling((max - min) / 2) + min
rank_at(cs, options, rank)
end
end

defp rank_at(cs, options, rank) do
Expand Down
23 changes: 23 additions & 0 deletions test/ranked_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,29 @@ defmodule EctoRanked.RankedTest do
Model.changeset(%Model{}, %{my_position: "wrong"}) |> Repo.insert!()
end
end

test "appending to list when @max is being used rebalances instead of shifting" do
for _i <- 1..31 do
%Model{} |> Model.changeset(%{}) |> Repo.insert!()
end

# Confirm the first 31 items perfectly spread between 0 and @max - 1
models = Model |> order_by(asc: :my_rank) |> Repo.all()
assert List.first(models).my_rank == 0
assert List.last(models).my_rank == @max - 1

# The 32nd item would leave no gap, so rebalance
%Model{} |> Model.changeset(%{}) |> Repo.insert!()
models = Model |> order_by(asc: :my_rank) |> Repo.all()
assert List.first(models).my_rank == -2_017_333_123
assert List.last(models).my_rank == 2_017_333_123

# Adding another item now fills the newly-created gap
%Model{} |> Model.changeset(%{}) |> Repo.insert!()
models = Model |> order_by(asc: :my_rank) |> Repo.all()
assert List.first(models).my_rank == -2_017_333_123
assert List.last(models).my_rank == 2_082_408_385
end
end

describe "updates" do
Expand Down

0 comments on commit 521e37f

Please sign in to comment.