Current section
Files
Jump to
Current section
Files
lib/paginator.ex
defmodule Magik.Paginator do
@moduledoc """
Support light paginate the query.
does not support counting total item because counting on large table is expensive
update: count total if needed
"""
import Ecto.Query
@default_config [size: 10, max_size: 100]
def paginate(query, params, opts \\ []) do
%{page: page_number, size: page_size} = cast_params(params)
count_total? = Map.get(params, "count_total") || Keyword.get(opts, :count_total, false)
repo = get_repo(opts)
entries = entries(query, page_number, page_size, repo)
total =
if count_total? do
count_entry(query, repo)
else
0
end
paginator = %{
page: page_number,
size: page_size,
total: total,
total_pages: ceil(total / page_size),
total_entries: total,
has_next?: length(entries) >= page_size,
has_prev?: page_number > 1
}
{entries, paginator}
end
@param_schema %{
page: :integer,
size: :integer
}
defp cast_params(params) do
{default_paging(), @param_schema}
|> Ecto.Changeset.cast(params, [:page, :size])
|> Ecto.Changeset.validate_number(:page, greater_than: 0)
|> Ecto.Changeset.validate_number(:size, greater_than: 0, less_than_or_equal_to: 80)
|> Ecto.Changeset.apply_action(:insert)
|> case do
{:ok, data} -> data
_err -> default_paging()
end
end
defp get_config(key, default \\ nil) do
config = Application.get_env(:magik, :pagination, [])
@default_config
|> Keyword.merge(config)
|> Keyword.get(key, default)
end
defp default_paging() do
%{
page: 1,
size: get_config(:size)
}
end
defp get_repo(opts) do
repo_module = Keyword.get(opts, :repo)
config_repo = get_config(:repo)
cond do
is_atom(repo_module) ->
repo_module
is_atom(config_repo) ->
config_repo
true ->
raise ":repo configuration for Paginator is missing"
end
end
defp entries(query, page_number, page_size, repo) do
offset = page_size * (page_number - 1)
query
|> limit(^page_size)
|> offset(^offset)
|> repo.all()
end
defp count_entry(query, repo) do
query
|> exclude(:order_by)
|> exclude(:preload)
|> exclude(:select)
|> exclude(:distinct)
|> select(
[e],
fragment(
"count(distinct ?)",
e.id
)
)
|> repo.one()
end
end