PhoenixKit.Utils.Reorder (phoenix_kit v2.52.2)

Copy Markdown View Source

Two-phase index rewrite for drag-to-reorder list views.

Given a list of UUIDs in their new display order and an Ecto schema, rewrites a position field on the matching rows to 1..N matching the order of the input list. The write runs in two passes inside a transaction — first to negative indices, then to positive — so a unique index on the position column (should one ever be added) wouldn't trip mid-update.

Consumers that need pre-write validation (scope checks, permission guards) or post-write side effects (activity logging, PubSub broadcasts) should wrap this helper rather than fold the logic in here. PhoenixKitProjects.reorder_projects/2 is the reference consumer doing exactly that — this module owns only the index-rewrite primitive.

Rows are matched on uuid by default; pass key: :id (any field) for a schema keyed some other way — an integer primary key, a slug. The payload's ids are cast with that field's Ecto type, so the strings a drag hook sends ("42") match an integer column.

Entries that are not a valid key (a non-UUID for :uuid, a non-integer for an integer key) are silently filtered — a stale or malformed drop event can't poison the rewrite. Duplicates dedup last-write-wins via Enum.uniq/1.

Example

defmodule MyApp.Endpoints do
  alias PhoenixKit.Utils.Reorder

  def reorder_endpoints(ordered_ids) do
    Reorder.reorder(MyApp.Endpoint, ordered_ids, :sort_order, repo: repo())
  end
end

Summary

Functions

Rewrites field on the rows whose uuid appears in ordered_ids, setting it to each UUID's 1-based position in the list.

Types

result()

@type result() :: {:ok, non_neg_integer()} | {:error, :too_many_uuids}

Functions

reorder(schema, ordered_ids, field, opts \\ [])

@spec reorder(module(), [String.t() | integer()], atom(), keyword()) :: result()

Rewrites field on the rows whose uuid appears in ordered_ids, setting it to each UUID's 1-based position in the list.

Returns {:ok, count} where count is the number of rows actually updated in the positive-write phase (matches Repo.update_all's count semantics — UUIDs in the payload that don't resolve to real rows aren't counted). Returns {:error, :too_many_uuids} when the dedup'd payload exceeds the configured cap. An empty / fully-filtered payload returns {:ok, 0}.

Options

  • :repo — the Ecto repo to use. Defaults to PhoenixKit.RepoHelper.repo/0 so it picks up the host app's repo.
  • :key — the field the ids are matched on. Default :uuid.
  • :max_ids (or the older :max_uuids) — payload cap, checked after dedup. Default 500. Guards against runaway drop events from a misbehaving client.