defmodule Ash.Reactor.ActionStep do @moduledoc """ The Reactor step which is used to execute generic actions. """ use Reactor.Step import Ash.Reactor.StepUtils alias Ash.{ActionInput, DataLayer} @doc false @impl true def run(arguments, context, options) do action_input_options = options |> maybe_set_kw(:authorize?, context[:authorize?]) |> maybe_set_kw(:actor, context[:actor]) |> maybe_set_kw(:tenant, context[:tenant]) |> maybe_set_kw(:tracer, context[:tracer]) |> maybe_set_kw(:authorize?, options[:authorize?]) |> maybe_set_kw(:actor, arguments[:actor]) |> maybe_set_kw(:tenant, arguments[:tenant]) |> Keyword.take(Ash.ActionInput.Opts.schema() |> Keyword.keys()) action_options = [domain: options[:domain]] |> maybe_set_kw(:authorize?, options[:authorize?]) options[:resource] |> ActionInput.for_action(options[:action], arguments[:input], action_input_options) |> ActionInput.set_context(arguments[:context] || %{}) |> Ash.run_action(action_options) |> case do :ok -> {:ok, :ok} other -> other end end @doc false @impl true def undo(record, arguments, context, options) do action_input_options = options |> maybe_set_kw(:authorize?, context[:authorize?]) |> maybe_set_kw(:actor, context[:actor]) |> maybe_set_kw(:tenant, context[:tenant]) |> maybe_set_kw(:tracer, context[:tracer]) |> maybe_set_kw(:authorize?, options[:authorize?]) |> maybe_set_kw(:actor, arguments[:actor]) |> maybe_set_kw(:tenant, arguments[:tenant]) |> Keyword.take(Ash.ActionInput.Opts.schema() |> Keyword.keys()) |> Keyword.drop([:resource]) |> Keyword.update( :skip_unknown_inputs, [:input_to_undo], &[:input_to_undo | &1] ) action_options = [] |> maybe_set_kw(:authorize?, options[:authorize?]) |> maybe_set_kw(:domain, options[:domain]) inputs = %{} |> Map.put(:result_to_undo, record) |> Map.put(:input_to_undo, arguments[:input]) options[:resource] |> ActionInput.for_action(options[:undo_action], inputs, action_input_options) |> ActionInput.set_context(arguments[:context] || %{}) |> Ash.run_action(action_options) |> case do {:ok, _} -> :ok other -> other end end @doc false @impl true def can?(%{impl: {_, options}}, :undo) do case options[:undo] do :always -> true :never -> false :outside_transaction -> !DataLayer.in_transaction?(options[:resource]) end end def can?(_, :compensate), do: false @doc false @impl true def async?(%{impl: {_, options}, async?: async}) do cond do DataLayer.in_transaction?(options[:resource]) -> false is_function(async, 1) -> async.(options) true -> async end end end