Best approach: update affecting other resources (multiple changesets from one action)

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.