Read action with preparation and keyset pagination

I have a read action that receives custom filters that are processed inside a prepare callback inside the read action.

read :list do
  argument(:filters, :term)

  prepare(fn input, context ->
    query =
      Pessoa
      |> Ash.Query.to_query()

    Enum.reduce(input.arguments.filters, query, fn
      {"nome", value}, query ->
        Ash.Query.filter(query, contains(nome, ^value))

      {"documento", value}, query ->
        Ash.Query.filter(query, contains(documento, ^value))

      filter, query ->
        Ash.Query.filter(query, ^filter)
    end)
  end)

  pagination keyset?: true, default_limit: 1, countable: true
end

I need this action returns an %Ash.Page.Keyset{} struct, but because of the prepare callback, it returns an %Ash.Page.Offset{} struct… If I remove the prepare function, it returns an %Ash.Page.Keyset{}.

I could not find in the docs how to use the prepare callback and the pagination macro inside the same action.

The first argument to the function passed to prepare is the query, can you try it like this:

  prepare(fn query, context ->
    query
    |> Ash.Query.get_argument(:filters)
    |> Enum.reduce(query, fn
      {"nome", value}, query ->
        Ash.Query.filter(query, contains(nome, ^value))

      {"documento", value}, query ->
        Ash.Query.filter(query, contains(documento, ^value))

      filter, query ->
        Ash.Query.filter(query, ^filter)
    end)
  end)

Thanks @barnabasJ.
My question is about the Keyset pagination and not about the filters. The filters are working well. Is the solution you provided the solution to the pagination problem (I could not try it yet)?

Yes, it is the solution. A preparation should always modify the provided query, not return a new query. The new query you returned in your callback is missing information used for pagination later on. By modifying the provided query instead, it won’t be missing context necessary for pagination.

1 Like

Nice. Worked. Thanks @barnabasJ @zachdaniel. I still have some hard time trying to understand the “behind the scenes” of Ash. Maybe today, on the YT live, I’m able to get to know more about that.