Read Actions

View Source

Read actions operate on an Ash.Query. Read actions always return lists of data. The act of pagination, or returning a single result, is handled as part of the interface, and is not a concern of the action itself. Here is an example of a read action:

# Giving your actions informative names is always a good idea
read :ticket_queue do
  # Use arguments to take in values you need to run your read action.
  argument :priorities, {:array, :atom} do
    constraints items: [one_of: [:low, :medium, :high]]
  end

  # This action may be paginated,
  # and returns a total count of records by default
  pagination offset: true, countable: :by_default

  # Arguments can be used in preparations and filters
  filter expr(status == :open and priority in ^arg(:priorities))
end

For a full list of all of the available options for configuring read actions, see the Ash.Resource.Dsl documentation.

Calling Read Actions

The basic formula for calling a read action looks like this:

Resource
|> Ash.Query.for_read(:action_name, %{argument: :value}, ...opts)
|> Ash.read!()

See below for variations on action calling, and see the code interface guide guide for how to define idiomatic and convenient functions that call your actions.

Ash.get!

The Ash.get! function is a convenience function for running a read action, filtering by a unique identifier, and expecting only a single result. It is equivalent to the following code:

# action can be omitted to use the primary read action 
Ash.get!(Resource, 1, action: :read_action)

# is roughly equivalent to

Resource
|> Ash.Query.filter(id == 1)
|> Ash.Query.limit(2)
|> Ash.Query.for_read(:read_action, %{})
|> Ash.read!()
|> case do
  [] -> # raise not found error
  [result] -> result
  [_, _] -> # raise too many results error
end

Ash.read_one!

The Ash.read_one! function is a similar convenience function to Ash.get!, but it does not take a unique identifier. It is useful when you expect an action to return only a single result, and want to enforce that and return a single result.

Ash.read_one!(query)

# is roughly equivalent to

query
|> Ash.Query.limit(2)
|> Ash.read!()
|> case do
  [] -> nil
  [result] -> result
  [_, _] -> # raise too many results error
end

Pagination

Ash provides built-in support for pagination when reading resources and their relationships. You can find more information about this in the pagination guide.

What happens when you call Ash.Query.for_read/4

The following steps are performed when you call Ash.Query.for_read/4.

What happens when you run the action

These steps are trimmed down, and are aimed at helping users understand the general flow. Some steps are omitted.

  • Run Ash.Query.for_read/3 if it has not already been run
  • Apply tenant filters for attribute
  • Apply pagination options
  • Run before action hooks
  • Multi-datalayer filter is synthesized. We run queries in other data layers to fetch ids and translate related filters to (destination_field in ^ids)
  • Strict Check & Filter Authorization is run
  • Data layer query is built and validated
  • Field policies are added to the query
  • Data layer query is Run
  • Authorizer "runtime" checks are run (you likely do not have any of these)

The following steps happen while(asynchronously) or after the main data layer query has been run

  • If paginating and count was requested, the count is determined at the same time as the query is run.
  • Any calculations & aggregates that were able to be run outside of the main query are run
  • Relationships, calculations, and aggregates are loaded