Ash FrameworkAF
Ash Framework3y ago
85 replies
\ ឵឵឵

Serialized access to instance of resource for `AshPostgres`

Pursuant to the discussion in #general , I'd like to create a macro which uses the changes hook to ensure that change actions to an instance (row) of a resource are serialized. For now I'm only looking at Postgres support, but if the lock primitive comes into play, happy to update. Primary changes are the addition of a definitely-not-working locking statement and or changeset.type == :read to not block read actions.

defmodule AshPostgresUtil.SerializedChanges do
  defmacro __using__(opts \\ []) do
    changes do
      change fn changeset, _ ->
        unless changeset.context[:locked][changeset.data.id] or changeset.type == :read do
          Ash.Changeset.before_action(changeset, fn changeset ->
            Ash.set_context(%{locked: %{changeset.data.id => true}})

            # something like this
            unquote(__CALLER__.module)
            |> lock("FOR UPDATE")
            |> unquote(opts[:repo]).get!(changeset.data.id)

            changeset
          end)
          |> Ash.Changeset.after_action(fn changeset, result ->
            Ash.set_context(%{locked: %{changeset.data.id => false}})
            {:ok, result}
          end)
        else
          changeset
        end
      end
    end
  end
end


Usage:
defmodule Bookstore.EnrollmentFlow do
  use Ash.Resource, data_layer: AshPostgres.DataLayer
  use AshPostgresUtil.SerializedChanges, repo: Bookstore.Repo

  # ...
end


Two things:
- This relies on the changeset and its children happening inside a transaction. Is that the case?
- I'm using the resource as if it's an Ecto schema. This is...maybe not legit. The Ash changeset (or parts of it) will end up as an Ecto changeset at some point, though. Is there an escape hatch I can use here to inject this or raw SQL into the changeset?
Was this page helpful?