Ash.ActionInput (ash v3.5.34)

View Source

Input for a custom action

Much like an Ash.Query and Ash.Changeset are used to provide inputs into CRUD actions, this struct provides the inputs required to execute a generic action.

Summary

Types

Function type for after action hooks.

Function type for after transaction hooks.

Function type for around transaction hooks.

Function type for before action hooks.

Function type for before transaction hooks.

t()

An action input struct for generic (non-CRUD) actions.

Functions

Adds an error to the errors list and marks the action input as invalid.

Adds an after_action hook to the action input.

Adds an after transaction hook to the action input.

Adds an around transaction hook to the action input.

Adds a before_action hook to the action input.

Adds a before transaction hook to the action input.

Deletes one or more arguments from the subject.

Fetches the value of an argument provided to the input.

Creates a new input for a generic action.

Gets the value of an argument provided to the input.

Creates a new action input from a resource.

Sets an argument value on the action input.

Deep merges the provided map into the input context.

Sets a private argument value on the action input.

Sets the tenant to use when calling the action.

Types

after_action_fun()

@type after_action_fun() :: (t(), term() ->
                         {:ok, term()}
                         | {:ok, term(), [Ash.Notifier.Notification.t()]}
                         | {:error, any()})

Function type for after action hooks.

Receives the action input and the result of the action, and can return the result optionally with notifications, or an error.

after_transaction_fun()

@type after_transaction_fun() :: (t(), {:ok, term()} | {:error, any()} ->
                              {:ok, term()} | {:error, any()})

Function type for after transaction hooks.

Receives the action input and the result of the transaction, and returns the result (potentially modified) or an error.

around_transaction_fun()

@type around_transaction_fun() :: (t(), (t() -> {:ok, term()} | {:error, any()}) ->
                               {:ok, term()} | {:error, any()})

Function type for around transaction hooks.

Receives an action input and a callback function that executes the transaction, and returns the result of calling the callback or an error.

before_action_fun()

@type before_action_fun() :: (t() ->
                          t()
                          | {t(),
                             %{notifications: [Ash.Notifier.Notification.t()]}})

Function type for before action hooks.

Receives an action input and returns a modified action input, optionally with notifications.

before_transaction_fun()

@type before_transaction_fun() :: (t() -> t() | {:error, any()})

Function type for before transaction hooks.

Receives an action input and returns a modified action input or an error.

t()

@type t() :: %Ash.ActionInput{
  action: Ash.Resource.Actions.Action.t() | nil,
  after_action: [after_action_fun()],
  after_transaction: [after_transaction_fun()],
  arguments: map(),
  around_transaction: [around_transaction_fun()],
  before_action: [before_action_fun()],
  before_transaction: [before_transaction_fun()],
  context: map(),
  domain: Ash.Domain.t(),
  errors: [Ash.Error.t()],
  invalid_keys: MapSet.t(),
  params: map(),
  resource: Ash.Resource.t(),
  tenant: term(),
  to_tenant: term(),
  valid?: boolean()
}

An action input struct for generic (non-CRUD) actions.

Contains all the information needed to execute a generic action including arguments, context, tenant information, validation state, and lifecycle hooks. Built using for_action/4 and modified with functions like set_argument/3 and set_context/2.

Functions

add_error(input, errors, path \\ [])

@spec add_error(
  t(),
  Ash.Error.error_input() | [Ash.Error.error_input()],
  Ash.Error.path_input()
) :: t()

Adds an error to the errors list and marks the action input as invalid.

This function allows you to add validation errors or other issues to the action input. Once an error is added, the input will be marked as invalid and action execution will be prevented.

Examples

# Add a simple string error
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:send_notification, %{})
...> |> Ash.ActionInput.add_error("Missing required configuration")
iex> input.valid?
false

# Add an error with a specific path
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:process_data, %{})
...> |> Ash.ActionInput.add_error("Invalid format", [:data, :format])
iex> input.errors |> List.first() |> Map.get(:path)
[:data, :format]

# Add multiple errors
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:complex_action, %{})
...> |> Ash.ActionInput.add_error(["Error 1", "Error 2"])
iex> length(input.errors)
2

# Add structured error with keyword list
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:validate_input, %{})
...> |> Ash.ActionInput.add_error(field: :email, message: "is invalid")

See also

  • Ash.Error.to_ash_error/3 for more on supported error values
  • Action implementations can use this to add custom validation errors
  • set_argument/3 automatically adds errors for invalid argument values

after_action(input, func, opts \\ [])

@spec after_action(
  input :: t(),
  fun :: after_action_fun(),
  opts :: Keyword.t()
) :: t()

Adds an after_action hook to the action input.

After action hooks are called with the action input and the result returned from the action. They can modify the result, perform side effects, or return errors to halt processing. The hook can return notifications alongside the result.

Examples

# Transform the result after action
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:calculate_stats, %{data: [1, 2, 3]})
...> |> Ash.ActionInput.after_action(fn input, result ->
...>   enhanced_result = Map.put(result, :calculated_at, DateTime.utc_now())
...>   {:ok, enhanced_result}
...> end)

# Log successful actions
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:important_action, %{})
...> |> Ash.ActionInput.after_action(fn inp, result ->
...>   Logger.info("Action completed successfully")
...>   {:ok, result}
...> end)

# Return notifications
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:notify_users, %{message: "Hello"})
...> |> Ash.ActionInput.after_action(fn input, result ->
...>   notification = %Ash.Notifier.Notification{
...>     resource: input.resource,
...>     action: input.action,
...>     data: result
...>   }
...>   {:ok, result, [notification]}
...> end)

# Handle errors
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:risky_action, %{})
...> |> Ash.ActionInput.after_action(fn input, result ->
...>   if is_error_result?(result) do
...>     {:error, "Action failed with custom error"}
...>   else
...>     {:ok, result}
...>   end
...> end)

See also

after_transaction(input, func, opts \\ [])

@spec after_transaction(
  input :: t(),
  fun :: after_transaction_fun(),
  opts :: Keyword.t()
) :: t()

Adds an after transaction hook to the action input.

After transaction hooks are executed after the transaction completes, regardless of success or failure. They receive both the input and the transaction result, and can modify the result.

Examples

# Add cleanup after transaction
iex> input
...> |> Ash.ActionInput.after_transaction(fn input, result ->
...>   cleanup_resources()
...>   result
...> end)

See also

around_transaction(input, func, opts \\ [])

@spec around_transaction(
  input :: t(),
  fun :: around_transaction_fun(),
  opts :: Keyword.t()
) :: t()

Adds an around transaction hook to the action input.

Around transaction hooks wrap the entire transaction execution. They receive a callback function that they must call to execute the transaction, allowing them to add logic both before and after the transaction.

Examples

# Add retry logic around transaction
iex> input
...> |> Ash.ActionInput.around_transaction(fn input, callback ->
...>   case callback.(input) do
...>     {:ok, result} -> {:ok, result}
...>     {:error, %{retryable?: true}} -> callback.(input) # Retry once
...>     error -> error
...>   end
...> end)

See also

before_action(input, func, opts \\ [])

@spec before_action(
  input :: t(),
  fun :: before_action_fun(),
  opts :: Keyword.t()
) :: t()

Adds a before_action hook to the action input.

Before action hooks are called with the action input and can modify it before the action executes. They can also add errors to halt processing or return notifications to be processed later.

Examples

# Validate arguments before action
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:send_notification, %{message: "Hello"})
...> |> Ash.ActionInput.before_action(fn input ->
...>   if String.length(input.arguments.message) > 100 do
...>     Ash.ActionInput.add_error(input, "Message too long")
...>   else
...>     input
...>   end
...> end)

# Set computed arguments
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:process_data, %{data: "test"})
...> |> Ash.ActionInput.before_action(fn input ->
...>   Ash.ActionInput.set_argument(input, :processed_at, DateTime.utc_now())
...> end)

# Return notifications
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:audit_action, %{})
...> |> Ash.ActionInput.before_action(fn input ->
...>   notification = %Ash.Notifier.Notification{
...>     resource: input.resource,
...>     action: input.action,
...>     data: %{audit: "before_action"}
...>   }
...>   {input, %{notifications: [notification]}}
...> end)

Options

  • prepend? - If true, adds the hook to the beginning of the list instead of the end

See also

before_transaction(input, func, opts \\ [])

@spec before_transaction(
  input :: t(),
  fun :: before_transaction_fun(),
  opts :: Keyword.t()
) :: t()

Adds a before transaction hook to the action input.

Before transaction hooks are executed before the transaction begins (if the action is transactional). They can modify the action input or halt execution by returning an error.

Examples

# Add logging before transaction
iex> input
...> |> Ash.ActionInput.before_transaction(fn input ->
...>   IO.puts("Starting transaction for action")
...>   input
...> end)

See also

delete_argument(input, argument_or_arguments)

@spec delete_argument(
  input :: t(),
  argument_or_arguments :: atom() | String.t() | [atom() | String.t()]
) :: t()

Deletes one or more arguments from the subject.

Parameters

  • subject - The subject to delete arguments from
  • arguments - Single argument name or list of argument names to delete

fetch_argument(input, argument)

@spec fetch_argument(t(), atom() | String.t()) :: {:ok, term()} | :error

Fetches the value of an argument provided to the input.

Returns {:ok, value} if the argument exists, or :error if not found. This is the safer alternative to get_argument/2 when you need to distinguish between a nil value and a missing argument.

Examples

# Fetch an argument that exists
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:send_notification, %{priority: :high})
...> |> Ash.ActionInput.fetch_argument(:priority)
{:ok, :high}

# Fetch an argument that doesn't exist
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:send_notification, %{})
...> |> Ash.ActionInput.fetch_argument(:missing_arg)
:error

# Distinguish between nil and missing arguments
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:example, %{optional_field: nil})
...> |> Ash.ActionInput.fetch_argument(:optional_field)
{:ok, nil}

# Use in conditional logic
iex> input = MyApp.Post |> Ash.ActionInput.for_action(:process, %{})
iex> case Ash.ActionInput.fetch_argument(input, :mode) do
...>   {:ok, mode} -> "Processing in #{mode} mode"
...>   :error -> "Using default processing mode"
...> end
"Using default processing mode"

See also

for_action(resource_or_input, action, params, opts \\ [])

@spec for_action(
  resource_or_input :: Ash.Resource.t() | t(),
  action :: atom(),
  params :: map(),
  opts :: Keyword.t()
) :: t()

Creates a new input for a generic action.

This is the primary way to create action inputs for generic actions. It validates the action exists, sets up the input with proper defaults, and validates any provided arguments according to the action's argument definitions.

Examples

# Create input for a simple action
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:send_notification, %{message: "Hello"})
%Ash.ActionInput{action: %{name: :send_notification}, arguments: %{message: "Hello"}, ...}

# Create input with options
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:complex_action, %{data: "test"},
...>   actor: current_user, authorize?: true)
%Ash.ActionInput{arguments: %{data: "test"}, ...}

# Create input and then modify it
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:example, %{})
...> |> Ash.ActionInput.set_context(%{source: "api"})
iex> input.action.name
:example

Options

  • :domain (Ash.Domain) - The domain to use for the action. The resource's domain is used by default.

  • :context (map/0) - Context to set on the action input. The default value is %{}.

  • :authorize? - Whether or not to run authorization on the action. Default behavior of this option is controlled by the domain.

  • :tenant (term/0) - The tenant to use for the action.

  • :scope (term/0) - A value that implements the Ash.Scope.ToOpts protocol, for passing around actor/tenant/context in a single value. See Ash.Scope.ToOpts for more.

  • :actor (term/0) - The actor performing the action

  • :skip_unknown_inputs - A list of unknown inputs to skip. Use :* to skip all unknown inputs.

  • :tracer (term/0) - A tracer or list of tracers to trace action execution.

  • :private_arguments (map/0) - A list of private arguments to be set before the action is invoked. The default value is %{}.

See also

get_argument(input, argument)

@spec get_argument(t(), atom() | String.t()) :: term()

Gets the value of an argument provided to the input.

Returns the argument value if found, or nil if not found. Arguments are validated and cast according to the action's argument definitions when set.

Examples

# Get an argument that exists
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:send_email, %{recipient: "user@example.com"})
...> |> Ash.ActionInput.get_argument(:recipient)
"user@example.com"

# Get an argument that doesn't exist returns nil
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:send_email, %{})
...> |> Ash.ActionInput.get_argument(:missing_arg)
nil

# Arguments can be accessed by string or atom key
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:example, %{"message" => "hello"})
...> |> Ash.ActionInput.get_argument(:message)
"hello"

See also

new(resource, domain \\ nil)

@spec new(Ash.Resource.t(), Ash.Domain.t()) :: t()

Creates a new action input from a resource.

This creates a basic action input struct that can be used as a starting point for building inputs for generic actions. Use for_action/4 to create an input bound to a specific action.

Examples

# Create a new action input for a resource
iex> Ash.ActionInput.new(MyApp.Post)
%Ash.ActionInput{resource: MyApp.Post, domain: nil, ...}


# Usually you'll want to use for_action/4 instead
iex> MyApp.Post |> Ash.ActionInput.for_action(:send_notification, %{message: "Hello"})
%Ash.ActionInput{action: %{name: :send_notification}, arguments: %{message: "Hello"}, ...}

See also

set_argument(input, argument, value)

@spec set_argument(input :: t(), name :: atom() | String.t(), value :: term()) :: t()

Sets an argument value on the action input.

The argument value is validated and cast according to the action's argument definition. If validation fails, errors will be added to the input and it will be marked as invalid.

Examples

# Set a simple argument
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:send_notification, %{})
...> |> Ash.ActionInput.set_argument(:message, "Hello World")
...> |> Ash.ActionInput.get_argument(:message)
"Hello World"

# Set multiple arguments by chaining
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:complex_action, %{})
...> |> Ash.ActionInput.set_argument(:priority, :high)
...> |> Ash.ActionInput.set_argument(:batch_size, 100)
iex> Ash.ActionInput.get_argument(input, :priority)
:high

# Arguments are validated according to type
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:schedule_job, %{})
...> |> Ash.ActionInput.set_argument(:run_at, ~U[2024-01-01 10:00:00Z])
iex> input.valid?
true

See also

set_context(input, map)

@spec set_context(t(), map() | nil) :: t()

Deep merges the provided map into the input context.

Context is used to pass additional information through the action pipeline that can be accessed by action implementations, changes, and validations. The context is merged deeply, so nested maps will be combined rather than replaced.

Do not use the private key in your custom context, as that is reserved for internal use.

Examples

# Set simple context values
iex> MyApp.Post
...> |> Ash.ActionInput.new()
...> |> Ash.ActionInput.set_context(%{source: "api", user_id: 123})
...> |> then(& &1.context.source)
"api"

# Context is merged deeply
iex> input = MyApp.Post
...> |> Ash.ActionInput.new()
...> |> Ash.ActionInput.set_context(%{metadata: %{version: 1}})
...> |> Ash.ActionInput.set_context(%{metadata: %{trace_id: "abc123"}})
iex> input.context.metadata
%{version: 1, trace_id: "abc123"}

# Use context in action implementations
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:process_data, %{data: "test"})
...> |> Ash.ActionInput.set_context(%{
...>   request_id: "req_456",
...>   feature_flags: %{new_algorithm: true}
...> })

See also

  • for_action/4 for setting context when creating inputs
  • Action implementations can access context for custom logic
  • set_tenant/2 for tenant-specific context

set_private_argument(input, name, value)

@spec set_private_argument(input :: t(), name :: atom(), value :: term()) :: t()

Sets a private argument value on the action input.

Private arguments are not exposed in the public API and can only be set internally. This function will only work for arguments marked as public?: false in the action definition.

Examples

# Set a private argument (assuming :internal_flag is private)
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:example, %{})
...> |> Ash.ActionInput.set_private_argument(:internal_flag, true)
...> |> Ash.ActionInput.get_argument(:internal_flag)
true

# Attempting to set a public argument as private will error
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:example, %{})
...> |> Ash.ActionInput.set_private_argument(:public_arg, "value")
iex> input.valid?
false

# Use in action implementations for internal state
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:complex_workflow, %{data: "user_data"})
...> |> Ash.ActionInput.set_private_argument(:workflow_step, 1)

See also

set_tenant(input, tenant)

@spec set_tenant(t(), Ash.ToTenant.t()) :: t()

Sets the tenant to use when calling the action.

In multitenant applications, this configures which tenant's data the action should operate on. The tenant value is used for data isolation and access control.

Examples

# Set tenant using a string identifier
iex> MyApp.Post
...> |> Ash.ActionInput.new()
...> |> Ash.ActionInput.set_tenant("org_123")
...> |> then(& &1.tenant)
"org_123"

# Set tenant using a struct that implements Ash.ToTenant
iex> org = %MyApp.Organization{id: 456}
iex> input = MyApp.Post
...> |> Ash.ActionInput.for_action(:send_notification, %{})
...> |> Ash.ActionInput.set_tenant(org)
iex> input.tenant == org
true

# Use with action execution
iex> MyApp.Post
...> |> Ash.ActionInput.for_action(:cleanup, %{})
...> |> Ash.ActionInput.set_tenant("tenant_456")
...> |> Ash.run_action()

See also