AE
Ash Elixir•3y ago
laznic

How to filter results when using keyset pagination?

After a lot of trial and error, I managed to get the next page for the keyset pagination in my project by using something like
next_page = MyApp.ResourceApi.page(
socket.assigns.items,
:next
)
next_page = MyApp.ResourceApi.page(
socket.assigns.items,
:next
)
This works perfectly fine, however I also have filters the user could apply to the items list. I got one solution working, however I'm not sure if it's the "proper" way to do it. I'm wondering if there would be a more cleaner solution to do it? My current solution is based on the small Filters guide in the Ash Documentation:
last_item = List.last(
socket.assigns.items.results
)

next_page = MyApp.ResourceApi.Resource.filter!(
filter_values.search,
filter_values.status,
page: [
limit: 16,
after: last_item.__metadata__.keyset
]
)

# Resource actions
actions do
read :filter do
argument :name, :string
argument :status, {:array, :string}

prepare build(sort: [id: :desc])
pagination countable: :by_default, keyset?: true, default_limit: 16

filter expr(
ilike(name, "%" <> ^arg(:name) <> "%")
and type(status, :string) in ^arg(:status)
)
end
end
last_item = List.last(
socket.assigns.items.results
)

next_page = MyApp.ResourceApi.Resource.filter!(
filter_values.search,
filter_values.status,
page: [
limit: 16,
after: last_item.__metadata__.keyset
]
)

# Resource actions
actions do
read :filter do
argument :name, :string
argument :status, {:array, :string}

prepare build(sort: [id: :desc])
pagination countable: :by_default, keyset?: true, default_limit: 16

filter expr(
ilike(name, "%" <> ^arg(:name) <> "%")
and type(status, :string) in ^arg(:status)
)
end
end
3 Replies
ZachDaniel
ZachDaniel•3y ago
Hey there! Arguments to the read action is one way to do it, but you can also provide a filtered query as a "starting point". For instance, if you just had. a read action with key set pagination available called :read
query =
if filter_values.search do
Ash.Query.filter(Resource, ilike(name "%" <> ^filter_values.search <> "%")
else
Resource
end

query =
if filter_values.status do
Ash.Query.filter(query, type(status, :string) in ^filter_values.status)
else
query
end

Resource.read!(query: query)
query =
if filter_values.search do
Ash.Query.filter(Resource, ilike(name "%" <> ^filter_values.search <> "%")
else
Resource
end

query =
if filter_values.status do
Ash.Query.filter(query, type(status, :string) in ^filter_values.status)
else
query
end

Resource.read!(query: query)
laznic
laznicOP•3y ago
Ah, that seems pretty nice. Thanks! Still a newbie in Elixir & Ash world, so gotta start keeping this kind of query approach in mind more 😄
ZachDaniel
ZachDaniel•3y ago
It all depends on how you want to design things, but this approach makes sense when you just want to do things like allow arbitrary filtering from a UI like this without having to add a bunch of structure to your resources 🙂

Did you find this page helpful?