How to reorder items in a list in Ash framework

You could use an action like this:

# sets the position and reorders
update :move_position do
  accept [:position]

  change YourResource.Changes.RepositionAll
end

update :decrement_position do
  change atomic_update(:position, position - 1)
end

update :increment_position do
  change atomic_update(:position, position - 1)
end

Some adaptation of the code you linked:

defmodule YourResource.Changes.RepositionAll do
  use Ash.Resource.Change
  require Ash.Query
  import Ash.Expr

  def change(changeset, opts, _) do
    changeset
    |> Ash.Changeset.before_action(changeset, fn changeset -> 
      requested_position = Ash.Changeset.get_attribute(changeset, :position)

      max_position =
        resource
        # put in whatever grouping you're sorting these within
        |> Ash.Query.filter(group_id == ^Ash.Changeset.get_attribute(changeset, :group_id))
        |> Ash.count!()

     new_position = min(requested_position, max_position)

      id = Ash.Changeset.get_attribute(changeset, :id)
     
      with %Ash.BulkResult{status: :succes} <- shift_down(id, new_position),
              %Ash.BulkResult{status: :succes} <- shift_up(id, new_position) do
         changeset
      else
         %Ash.BulkResult{errors: errors} -> Ash.Changeset.add_error(changeset, errors)
      end
    end)
  end

  def shift_down(resource, id, new_position) do
    resource
    |> Ash.Query.filter(position > ^old_position(id) and position <= new_position)
    |> Ash.Query.bulk_update!(:decrement_position)
  end

  def shift_up(id, new_position) do
    resource
    |> Ash.Query.filter(position < ^old_position(id) and position > new_position)
    |> Ash.Query.bulk_update!(:decrement_position)
  end

  def old_position(id) do
    expr(fragment("SELECT position FROM table WHERE id = ?", ^id))
  end
end

One small note:

old_position = from(og in type, where: og.id == ^struct.id, select: og.position)

stating the above is currently a limitation in Ash expressions, that will be addressed soon: Support resources as aggregate targets in expressions · Issue #939 · ash-project/ash · GitHub

So for that reason, I’ve done it as a SQL fragment, which is not ideal.

I didn’t run any of the above code, but it should be a reasonable place to get started :slight_smile:

4 Likes