Using custom Prepare on Read on Resource with after_action trasnformation on Result

Customer Prepare:

@impl true
  def prepare(query, opts, _context) do
    query
    |> Ash.Query.after_action(fn _query, results ->
      Enum.map(results, fn result ->
        {Map.fetch!(result, opts[:name]), Map.fetch!(result, opts[:id])}
      end)
    end)
  end

call it in resource as follows

 read :lookup do
      prepare {WB.Ash.Preperations.Lookup, [id: :id, name: :country]}
    end

The result is getting transformed but the out put is errorring as :

Bread Crumbs:
  > Exception raised in: WB.Ash.Organization.Local.lookup

Unknown Error

* ** (CaseClauseError) no case clause matching: [{"Botswana", "019600e8-fa87-7f84-9ee0-aadfb43c2f61"}...]

Return {:ok, results}

1 Like

Zach - thanks for reply but not sure where need to return {:ok, results} - on the action or on the prepere or within the action or within prepare

In your Custom Preparation Lookup.
Which you can even generate, at least the boilerplate that is. :sunglasses:

THanks Ken but still no luck.

Tried as follows:

 @impl true
  def prepare(query, opts, _context) do
   res =  query
    |> Ash.Query.after_action(fn _query, results ->
   
        Enum.map(results, fn result ->
          {Map.fetch!(result, opts[:name]), Map.fetch!(result, opts[:id])}
        end)
    end)
   [:ok, res}
  end

And As follows:

 @impl true
  def prepare(query, opts, _context) do
    query
    |> Ash.Query.after_action(fn _query, results ->
   
        res  = Enum.map(results, fn result ->
          {Map.fetch!(result, opts[:name]), Map.fetch!(result, opts[:id])}
        end)
    {:ok, res}
    end)
  end

Not sure how else to do it.

Apologies :bowing_man:, I was busy when I originally posted, I should have been more detailed.

Your second example is the correct usage of returning {:ok, results}, except its got one critical issue, which is that you must return a list of structs matching the resource from a read action. If you want to have an action that returns something else, you need to use a generic action.

# we don't have a tuple type yet, but you could add one or just use `:term`
action :lookup, {:array, :term} do
  argument :id, :uuid, allow_nil?: false

  run fn input, _ -> 
    case do_lookup_with_read(input.arguments.id) do
      {:ok, results} -> {:ok, Enum.map(results, &{&1.id, &1.name})}
      {:error, error} -> {:error, error}
    end
  end
end

Something like that. It’s not the exact composition you are looking for because it seems like you’re trying to make a reusable preparation, but what you want has to be done with generic actions, not read actions. You could define a custom action implementation like so:

defmodule YourModule do
  use Ash.Resource.Actions.Implementation

  def run(input, opts, context) do
    {:ok, "Hello"}
  end
end

and then use it generically with something like:

action :lookup, {:array, :term} do
  argument :id, :uuid, allow_nil?: false

  run {YourImplementation, id: :id, name: :country, read_action: :read_action}
end
1 Like