Packages

A grab bag of utilities I tend to want, so I can build faster.

Current section

Files

Jump to
fast lib pagination pagination.ex
Raw

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 ->
cursor =
items
|> Enum.at(-1)
|> get_cursor(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
def get_cursor(item, 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 ->
item.id
fun ->
fun.(item)
end
end
end