From 7de83e8bb1e56be47fd387d82bbc86c55325855e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 May 2023 14:59:47 -0400 Subject: [PATCH] improvement: add `get_and_lock/1` builtin change --- lib/ash/resource/change/builtins.ex | 16 ++++++++++++++ lib/ash/resource/change/get_and_lock.ex | 28 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 lib/ash/resource/change/get_and_lock.ex diff --git a/lib/ash/resource/change/builtins.ex b/lib/ash/resource/change/builtins.ex index 1758218e..189ebd22 100644 --- a/lib/ash/resource/change/builtins.ex +++ b/lib/ash/resource/change/builtins.ex @@ -107,6 +107,22 @@ defmodule Ash.Resource.Change.Builtins do {Ash.Resource.Change.GetAndLockForUpdate, []} end + @doc """ + Re-fetches the record being updated and locks it for update. + + Only usable with data layers that support locking `:for_update`. + + This happens in a `before_action` hook (so that it is done as part of the transaction). + + If your resource has global validations (in the top level `validations` block), you may + want to add `delay_global_validations? true` to your action to ensure they happen on the + locked record. + """ + @spec get_and_lock(lock :: Ash.DataLayer.lock_type()) :: Ash.Resource.Change.ref() + def get_and_lock(lock) do + {Ash.Resource.Change.GetAndLock, [lock: lock]} + end + @doc """ Sets the attribute to the value provided. diff --git a/lib/ash/resource/change/get_and_lock.ex b/lib/ash/resource/change/get_and_lock.ex new file mode 100644 index 00000000..e8ba2c03 --- /dev/null +++ b/lib/ash/resource/change/get_and_lock.ex @@ -0,0 +1,28 @@ +defmodule Ash.Resource.Change.GetAndLock do + @moduledoc """ + Refetches the record being updated or destroyed, and locks it with the given type. + """ + use Ash.Resource.Change + + def change(changeset, opts, context) do + Ash.Changeset.before_action(changeset, fn changeset -> + primary_key = Ash.Resource.Info.primary_key(changeset.resource) + pkey_values = changeset.data |> Map.take(primary_key) |> Map.to_list() + + case changeset.api.get( + changeset.resource, + pkey_values, + tracer: context[:tracer], + tenant: context[:tenant], + authorize?: false, + lock: opts[:lock] + ) do + {:ok, record} -> + %{changeset | data: record} + + {:error, error} -> + Ash.Changeset.add_error(changeset, error) + end + end) + end +end