ash/documentation/topics/actions/generic-actions.md
Zach Daniel d2ea5bb108 docs: rewrite action docs
improvement: synthesize attributes from atomics for better notifications
2024-04-07 08:02:10 -04:00

2.3 KiB

Generic Actions

Generic actions are so named because there are no special rules about how they work. A generic action takes arguments and returns a value. The struct used for building input for a generic action is Ash.ActionInput.

action :say_hello, :string do
  argument :name, :string, allow_nil?: false

  run fn input, _ ->
    {:ok, "Hello: #{input.arguments.name}"
  end
end

A generic action declares its arguments, return type, and implementation, as illustrated above.

Why use generic actions?

The example above could be written as a normal function in elixir, i.e

def say_hello(name), do: "Hello: #{name}"

The benefit of using generic actions instead of defining normal functions:

  • They can be used with api extensions like ash_json_api and ash_graphql
  • Their inputs are type checked and casted
  • They support Ash authorization patterns (i.e policies)
  • They can be included in the code interface of a resource
  • They can be made transactional with a single option (transaction? true)

If you don't need any of the above, then there is no problem with writing regular Elixir functions!

Return types and constraints

Generic actions do not cast their return types. It is expected that the action return a valid value for the type that they declare. However, declaring additional constraints can inform API usage, and make the action more clear. For example:

action :priority, :integer do
  constraints [min: 1, max: 3]
  argument :status, :atom, constraints: [one_of: [:high, :medium, :low]]

  run fn input, _ ->
    case input.arguments.status do
      :high -> {:ok, 3}
      :medium -> {:ok, 2}
      :low -> {:ok, 1}
    end
  end
end

Returning resource instances {: .tip}

It sometimes happens that you want to make a generic action which returns an instance or instances of the resource. It's natural to assume that you can set your action's return type to the name of your resource. Unfortunately this will result in a compile error as the resource struct is not yet defined at the time of DSL transformation. The work around is to define an action that returns :struct and is constrained to only be of a specific type, eg:

action :get, :struct do
  constraints instance_of: __MODULE__

  run # ...
end