Hello! At the moment, Ash does not have bulk updates, so this is in fact the best way to do this if you want to use only Ash (i.e iterating each and updating its position). Ash will have bulk updates (updates with a query that execute as a single update statement), but until then, I’d suggest using Ecto directly.
defmodule Api.Column.Preparations.SetNumber do
def change(changeset, _, _) do
id = changeset.data.id
current_number = Ash.Changeset.get_attribute(changeset, :number)
new_number = Ash.Changeset.get_argument(changeset, :new_number)
# change the target column to it's new position
Ash.Changeset.change_attribute(changeset, :column, new_number)
# use a before action hook
Ash.Changeset.before_action(changeset, fn changeset ->
{:ok, bumping_forward_query} =
Api.Column
|> Ash.Query.filter(number >= ^new_column and id != ^id)
|> Ash.Query.data_layer_query()
{:ok, bumping_backwards_query} =
Api.Column
|> Ash.Query.filter(number <= ^new_number and number >= current_number and id != ^id)
|> Ash.Query.data_layer_query()
YourRepo.update(bumping_forward_query, inc: [number: 1])
YourRepo.update(bumping_backwards_query, dec: [number: -1])
changeset
end)
end
You can also simplify by removing the argument and just detecting the column changing
defmodule Api.Column.Preparations.SetNumber do
def change(changeset, _, _) do
if Ash.Changeset.changing_attribute?(changeset, :number) do
id = changeset.data.id
current_number = changeset.data.number
new_number = Ash.Changeset.get_attribute(changeset, :number)
# use a before action hook
Ash.Changeset.before_action(changeset, fn changeset ->
{:ok, bumping_forward_query} =
Api.Column
|> Ash.Query.filter(number >= ^new_column and id != ^id)
|> Ash.Query.data_layer_query()
{:ok, bumping_backwards_query} =
Api.Column
|> Ash.Query.filter(number <= ^new_number and number >= current_number and id != ^id)
|> Ash.Query.data_layer_query()
YourRepo.update(bumping_forward_query, inc: [number: 1])
YourRepo.update(bumping_backwards_query, dec: [number: -1])
changeset
end)
else
changeset
end
end
I haven’t run this code, but it should be essentially what you need.
FWIW there are strategies around sorted relationships that use sparse sets of numbers and/or floating point numbers very cleverly to avoid having to rebalance things often. I’d like to make an extension at some point that will add that capability automatically to a relationship.






















