Current section

Files

Jump to
kerosene lib kerosene.ex
Raw

lib/kerosene.ex

defmodule Kerosene do
defstruct items: [], per_page: 0, max_page: 0, page: 0, total_pages: 0, total_count: 0, params: []
import Ecto.Query
@per_page 10
@max_page 100
@page 1
@moduledoc """
Pagination for Ecto and Phoenix.
"""
defmacro __using__(opts \\ []) do
quote do
def paginate(query, params \\ %{}, options \\ []) do
Kerosene.paginate( __MODULE__, query, params,
Keyword.merge(unquote(opts), options))
end
end
end
def paginate(repo, query, params, opts) do
paginate(repo, query, build_options(opts, params))
end
def paginate(repo, query, opts) do
per_page = Keyword.get(opts, :per_page)
max_page = Keyword.get(opts, :max_page)
total_count = get_total_count(opts[:total_count], repo, query)
total_pages = get_total_pages(total_count, per_page)
page = get_page(opts, total_pages)
offset = get_offset(total_count, page)
kerosene = %Kerosene {
per_page: per_page,
page: page,
total_pages: total_pages,
total_count: total_count,
max_page: max_page,
params: opts[:params]
}
{get_items(repo, query, per_page, offset), kerosene}
end
defp get_items(repo, query, nil, _), do: repo.all(query)
defp get_items(repo, query, limit, offset) do
query
|> limit(^limit)
|> offset(^offset)
|> repo.all
end
defp get_offset(total_pages, page) do
page = case page > total_pages do
true -> total_pages
_ -> page
end
case page > 0 do
true -> page - 1
_ -> page
end
end
defp get_total_count(count, _repo, _query) when is_integer(count) and count >= 0, do: count
defp get_total_count(_count, repo, query) do
total_pages =
query
|> exclude(:preload)
|> exclude(:order_by)
|> total_count()
|> repo.one()
total_pages || 0
end
defp total_count(query = %{group_bys: [_|_]}) do
query
|> subquery()
|> select(count("*"))
end
defp total_count(query) do
primary_key = get_primary_key(query)
query
|> exclude(:select)
|> select([i], count(field(i, ^primary_key), :distinct))
end
def get_primary_key(query) do
new_query = case is_map(query) do
true -> query.from |> elem(1)
_ -> query
end
new_query
|> apply(:__schema__, [:primary_key])
|> hd
end
def get_total_pages(_, nil), do: 1
def get_total_pages(count, per_page) do
Float.ceil(count / per_page) |> trunc()
end
def get_page(params, total_pages) do
case params[:page] > params[:max_page] do
true -> total_pages
_ -> params[:page]
end
end
defp build_options(opts, params) do
page = Map.get(params, "page", @page) |> to_integer()
per_page = default_per_page(opts) |> to_integer()
max_page = Keyword.get(opts, :max_page, default_max_page())
Keyword.merge(opts, [page: page, per_page: per_page, params: params, max_page: max_page])
end
defp default_per_page(opts) do
case Keyword.get(opts, :per_page) do
nil -> Application.get_env(:kerosene, :per_page, @per_page)
per_page -> per_page
end
end
defp default_max_page() do
Application.get_env(:kerosene, :max_page, @max_page)
end
def to_integer(i) when is_integer(i), do: abs(i)
def to_integer(i) when is_binary(i) do
case Integer.parse(i) do
{n, _} -> n
_ -> 0
end
end
def to_integer(_), do: @page
end