Current section
Files
Jump to
Current section
Files
lib/pagination/pagination.ex
defmodule Fast.Pagination do
@default_max_page_size 100
def paginate(args, fun, opts \\ []) do
# First of all, don't let the user ask for a zillion records... 100 is max.
# Second of all, ask for an extra 1 record, but then don't send it back to
# the client. The presence of the extra 1 indicates that the end_of_list
# hasn't been reached and there's more the user can ask to load.
max_page_size = Keyword.get(opts, :max_page_size, @default_max_page_size)
args =
if args.first > max_page_size do
Map.put(args, :first, max_page_size + 1)
else
Map.put(args, :first, args.first + 1)
end
with items when is_list(items) <- fun.(args) do
end_of_list = length(items) < args.first
items = Enum.take(items, args.first - 1)
case items do
[] ->
{:ok, %{cursor: nil, items: items, end_of_list: end_of_list}}
items when is_list(items) and length(items) > 0 ->
extra_item = Enum.at(items, -1)
cursor = get_cursor(extra_item, items, opts)
{:ok, %{cursor: cursor, items: items, end_of_list: end_of_list}}
end
end
end
# This is a useful way to stream through pages without
# requiring it to be inside a single transaction.
# If
def stream_pages(fun, opts \\ []) do
Stream.resource(
fn -> %{first: 100} end,
fn
:end_of_list ->
{:halt, %{}}
acc ->
{:ok, page} = paginate(acc, fun, opts)
next_acc =
case page.end_of_list do
true ->
:end_of_list
false ->
Map.put(acc, :after, page.cursor)
end
{[page], next_acc}
end,
fn _ -> %{} end
)
end
defp get_cursor(extra_item, items, opts) do
# NB: Yes, this could be written in a more "clever" way, but it's already funny
# enough with the function-as-option, so I'm intentionally writing it to
# be super explicit.
case Access.get(opts, :get_cursor) do
nil ->
extra_item.id
func when is_function(func) ->
case :erlang.fun_info(func)[:arity] do
1 -> func.(extra_item)
2 -> func.(extra_item, items)
end
end
end
end