AE
Ash Elixir•2y ago
Jason

What happens when :prev page request is used but there is no previous page?

Need help understanding how keyset pagination works. Here are the steps I took. 1. 'search-change' handle_event does the initial search properly. The query output is saved in a variable called page. 2. When the 'prev'' button is clicked triggering 'prev-page' handler, (as expected) nothing happens. 3. When the "next" button is clicked triggering 'next-page' handler, nothing happens. I expected it to return the iintial search results again, or the set after that.
def handle_event("search-change", %{"search" => search}, socket) do

query = Ash.Query.filter(User, ilike(username, "%" <> ^search <> "%"))

page = User.read!(query: query)

searched_users = page.results

{:noreply,
socket
|> assign(:page, page)
|> stream(:searched_users, searched_users, reset: true)}
end

def handle_event("prev-page", params, socket) do
page = MyApp.Accounts.page!(socket.assigns.page, :prev)

{:noreply,
socket
|> assign(:page, page)
|> stream(:searched_users, page.results, at: 0)}
end

def handle_event("search-change", %{"search" => search}, socket) do

query = Ash.Query.filter(User, ilike(username, "%" <> ^search <> "%"))

page = User.read!(query: query)

searched_users = page.results

{:noreply,
socket
|> assign(:page, page)
|> stream(:searched_users, searched_users, reset: true)}
end

def handle_event("prev-page", params, socket) do
page = MyApp.Accounts.page!(socket.assigns.page, :prev)

{:noreply,
socket
|> assign(:page, page)
|> stream(:searched_users, page.results, at: 0)}
end

` Do I need to do something to prevent :prev page request where there is no previous page? If so, how do I do that? I saw this example, but thought page request is a simpler way to do the same.
next_page = Api.read(Resource, page: [limit: 10, after: last_record.__metadata__.keyset])
next_page = Api.read(Resource, page: [limit: 10, after: last_record.__metadata__.keyset])
4 Replies
ZachDaniel
ZachDaniel•2y ago
🤔 interesting...I think ideally a test case in our pagination tests illustrating this behavior would be the most helpful
Jason
JasonOP•2y ago
Thanks! I will take a look. This test fails. It's probably my expectation that's faulty. What am I missing? BTW, in a similar test, I noticed Offset pagination returns %{name: "4"} for :prev. which is what I would have liked.
test "the prev request right after the initial query remains the same as the initial result (like offset pagination)" do
assert %{results: [%{name: "4"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(action: :keyset, page: [limit: 1])

IO.inspect(page, label: "page first #######")

assert %{results: [[%{name: "4"]} = page = Api.page!(page, :prev)
IO.inspect(page, label: "page prev #######")

end
test "the prev request right after the initial query remains the same as the initial result (like offset pagination)" do
assert %{results: [%{name: "4"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(action: :keyset, page: [limit: 1])

IO.inspect(page, label: "page first #######")

assert %{results: [[%{name: "4"]} = page = Api.page!(page, :prev)
IO.inspect(page, label: "page prev #######")

end
This is the output when inspecting the result of :prev.
page prev #######: %Ash.Page.Keyset{
results: [],
count: 5,
before: "g2wAAAACbQAAAAE0bQAAACRiNTUyZWZkNC1mZmQ2LTQxZWUtYjM3NC0wYzdiZmVkMDBlZGFq",
after: nil,
limit: 1,
rerun: {#Ash.Query<
resource: MyApp.PaginationTest.User,
filter: #Ash.Filter<name in ["0", "1", "2", "3", "4"]>,
sort: [name: :desc, id: :asc],
limit: 2
>,
[
page: [
before: "g2wAAAACbQAAAAE0bQAAACRiNTUyZWZkNC1mZmQ2LTQxZWUtYjM3NC0wYzdiZmVkMDBlZGFq",
limit: 1
],
verbose?: false,
actor: nil,
authorize?: false,
return_query?: false
]},
more?: false
}
page prev #######: %Ash.Page.Keyset{
results: [],
count: 5,
before: "g2wAAAACbQAAAAE0bQAAACRiNTUyZWZkNC1mZmQ2LTQxZWUtYjM3NC0wYzdiZmVkMDBlZGFq",
after: nil,
limit: 1,
rerun: {#Ash.Query<
resource: MyApp.PaginationTest.User,
filter: #Ash.Filter<name in ["0", "1", "2", "3", "4"]>,
sort: [name: :desc, id: :asc],
limit: 2
>,
[
page: [
before: "g2wAAAACbQAAAAE0bQAAACRiNTUyZWZkNC1mZmQ2LTQxZWUtYjM3NC0wYzdiZmVkMDBlZGFq",
limit: 1
],
verbose?: false,
actor: nil,
authorize?: false,
return_query?: false
]},
more?: false
}
ZachDaniel
ZachDaniel•2y ago
So I think the limitation is effectively that, unlikely offset pagination, we don't actually know here if there is a previous page or not its one of the trade-offs of keyset based pagination we can technically fake it for just this one exact instance where you have a page w/ no before and you ask for the prev page but that won't solve the problem everywhere because if you went forward one page, back one page, then you'd have a before. okay, I have an idea I'll change the pagination code where if the next page is empty, we'll just return the previous page okay, just pushed some changes to ash main, lets see how you like it
Jason
JasonOP•2y ago
Thanks! Let me try it and report back. It's working beautifully. Thank you!

Did you find this page helpful?