Current section
Files
Jump to
Current section
Files
lib/quarto.ex
defmodule Quarto do
@external_resource "./README.md"
@moduledoc """
#{File.read!(@external_resource) |> String.split("---", parts: 2) |> List.last()}
"""
defmacro __using__(opts) do
quote do
@defaults unquote(opts)
def paginate(queryable, opts \\ [], repo_opts \\ []) do
opts = Keyword.merge(@defaults, opts)
Quarto.paginate(queryable, opts, __MODULE__, repo_opts)
end
end
end
@spec paginate(Ecto.Query.t(), nil | maybe_improper_list, Ecto.Repo.t(), Keyword.t()) ::
Quarto.Page.t()
@doc """
Paginate/4
Fetches all the results matching the query within the cursors.
Options
* `:after` - Fetch the records after this cursor.
* `:before` - Fetch the records before this cursor.
* `:coalesce` - Function that receives the field
* `:cursor` - Module to use for encoding/decoding the cursor
* `:cursor_fields` - Module to use for building the cursor from a record
* `:include_total_count` - Set this to true to return the total number of records matching the query. Note that this number will be capped by :total_count_limit. Defaults to false.
* `:total_count_primary_key_field` - Running count queries on specified column of the table
* `:limit` - Limits the number of records returned per page. Note that this number will be capped by :maximum_limit. Defaults to `50`.
* `:maximum_limit` - Sets a maximum cap for :limit. This option can be useful when :limit is set dynamically (e.g from a URL param set by a user) but you still want to enfore a maximum. Defaults to 500.
* `:total_count_limit` - Running count queries on tables with a large number of records is expensive so it is capped by default. Can be set to `:infinity` in order to count all the records. Defaults to 10,000.
Repo options
This will be passed directly to Ecto.Repo.all/2, as such any option supported by this function can be used here.
"""
def paginate(queryable, opts, repo, repo_opts \\ []) do
config = Quarto.Config.new([queryable: queryable] ++ opts)
sorted_entries = entries(queryable, config, repo, repo_opts)
paginated_entries = paginate_entries(sorted_entries, config)
{total_count, total_count_cap_exceeded} =
Quarto.Ecto.QueryTotal.total_count(queryable, config, repo, repo_opts)
%Quarto.Page{
entries: paginated_entries,
metadata: %Quarto.Page.Metadata{
after: after_cursor(paginated_entries, sorted_entries, config),
before: before_cursor(paginated_entries, sorted_entries, config),
limit: config.limit,
total_count: total_count,
total_count_cap_exceeded: total_count_cap_exceeded
}
}
end
@doc """
Build the cursor for a given entry
See also `Quarto.CursorValue`
"""
@spec cursor_for_entry(map, Quarto.Config.t()) :: any
def cursor_for_entry(entry, config) do
build_cursor_value(entry, config) |> config.cursor.encode!(config)
end
defp build_cursor_value(entry, %{cursor_builder: {m, f, _}} = config) do
Kernel.apply(m, f, [entry, config])
end
defp after_cursor([], [], _config), do: nil
defp after_cursor(paginated_entries, _sorted_entries, %{before: c_before} = config)
when not is_nil(c_before) do
last_or_nil(paginated_entries, config)
end
defp after_cursor(paginated_entries, sorted_entries, config) do
if last_page?(sorted_entries, config) do
nil
else
last_or_nil(paginated_entries, config)
end
end
defp before_cursor([], [], _config), do: nil
defp before_cursor(_paginated_entries, _sorted_entries, %{after: nil, before: nil}),
do: nil
defp before_cursor(paginated_entries, _sorted_entries, %{after: c_after} = config)
when not is_nil(c_after) do
first_or_nil(paginated_entries, config)
end
defp before_cursor(paginated_entries, sorted_entries, config) do
if first_page?(sorted_entries, config) do
nil
else
first_or_nil(paginated_entries, config)
end
end
defp first_or_nil(entries, config) do
if first = List.first(entries) do
cursor_for_entry(first, config)
else
nil
end
end
defp last_or_nil(entries, config) do
if last = List.last(entries) do
cursor_for_entry(last, config)
else
nil
end
end
defp first_page?(sorted_entries, %{limit: limit}) do
Enum.count(sorted_entries) <= limit
end
defp last_page?(sorted_entries, %{limit: limit}) do
Enum.count(sorted_entries) <= limit
end
defp entries(queryable, config, repo, repo_opts) do
queryable
|> Quarto.Ecto.Query.paginate(config)
|> repo.all(repo_opts)
end
defp paginate_entries(sorted_entries, %{after: nil, before: before, limit: limit})
when not is_nil(before) do
sorted_entries
|> Enum.take(limit)
|> Enum.reverse()
end
defp paginate_entries(sorted_entries, %{limit: limit}) do
Enum.take(sorted_entries, limit)
end
end