Skip to content

Commit 86ebcef

Browse files
committed
add page_type to Config. allow caller to get more efficient SimplePage
1 parent bc97f6b commit 86ebcef

File tree

4 files changed

+628
-11
lines changed

4 files changed

+628
-11
lines changed

lib/scrivener/paginater/ecto/query.ex

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
defimpl Scrivener.Paginater, for: Ecto.Query do
22
import Ecto.Query
33

4-
alias Scrivener.{Config, Page}
4+
alias Scrivener.{Config, Page, SimplePage}
55

66
@moduledoc false
77

8-
@spec paginate(Ecto.Query.t(), Scrivener.Config.t()) :: Scrivener.Page.t()
8+
@spec paginate(Ecto.Query.t(), Scrivener.Config.t()) ::
9+
Scrivener.Page.t() | Scrivener.SimplePage.t()
910
def paginate(query, %Config{
10-
page_size: page_size,
11-
page_number: page_number,
11+
page_type: :normal,
1212
module: repo,
1313
caller: caller,
14+
page_number: page_number,
15+
page_size: page_size,
1416
options: options
1517
}) do
1618
total_entries =
@@ -26,25 +28,59 @@ defimpl Scrivener.Paginater, for: Ecto.Query do
2628
page_number =
2729
if allow_overflow_page_number, do: page_number, else: min(total_pages, page_number)
2830

31+
entries =
32+
if page_number > total_pages,
33+
do: [],
34+
else: entries(query, repo, page_number, page_size, options)
35+
2936
%Page{
3037
page_size: page_size,
3138
page_number: page_number,
32-
entries: entries(query, repo, page_number, total_pages, page_size, options),
39+
entries: entries,
3340
total_entries: total_entries,
3441
total_pages: total_pages
3542
}
3643
end
3744

38-
defp entries(_query, _repo, page_number, total_pages, _page_size, _options)
39-
when page_number > total_pages,
40-
do: []
45+
def paginate(query, %Config{
46+
page_type: :simple,
47+
module: repo,
48+
page_number: page_number,
49+
page_size: page_size,
50+
options: options
51+
}) do
52+
entries_with_maybe_one_extra =
53+
entries(query, repo, page_number, page_size, options, extra_entry_size: 1)
54+
55+
{entries, has_more} =
56+
if length(entries_with_maybe_one_extra) > page_size do
57+
entries =
58+
entries_with_maybe_one_extra
59+
|> Enum.reverse()
60+
|> tl()
61+
|> Enum.reverse()
62+
63+
{entries, true}
64+
else
65+
{entries_with_maybe_one_extra, false}
66+
end
67+
68+
%SimplePage{
69+
page_size: page_size,
70+
page_number: page_number,
71+
entries: entries,
72+
has_more: has_more
73+
}
74+
end
4175

42-
defp entries(query, repo, page_number, _total_pages, page_size, options) do
76+
defp entries(query, repo, page_number, page_size, options, opts \\ []) do
77+
extra_entry_size = Keyword.get(opts, :extra_entry_size, 0)
4378
offset = Keyword.get_lazy(options, :offset, fn -> page_size * (page_number - 1) end)
79+
limit = page_size + extra_entry_size
4480

4581
query
4682
|> offset(^offset)
47-
|> limit(^page_size)
83+
|> limit(^limit)
4884
|> repo.all(options)
4985
end
5086

lib/scrivener/simple_page.ex

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
defmodule Scrivener.SimplePage do
2+
@moduledoc """
3+
Similar as `Scrivener.Page`, but without the `total_entries` and `total_pages` fields and with a `has_more` field instead.
4+
"""
5+
6+
defstruct [:page_number, :page_size, :has_more, entries: []]
7+
8+
@type t :: %__MODULE__{
9+
entries: list(),
10+
page_number: pos_integer(),
11+
page_size: integer(),
12+
has_more: boolean()
13+
}
14+
@type t(entry) :: %__MODULE__{
15+
entries: list(entry),
16+
page_number: pos_integer(),
17+
page_size: integer(),
18+
has_more: boolean()
19+
}
20+
21+
defimpl Enumerable do
22+
@spec count(Scrivener.SimplePage.t()) :: {:error, Enumerable.Scrivener.SimplePage}
23+
def count(_page), do: {:error, __MODULE__}
24+
25+
@spec member?(Scrivener.SimplePage.t(), term) :: {:error, Enumerable.Scrivener.SimplePage}
26+
def member?(_page, _value), do: {:error, __MODULE__}
27+
28+
@spec reduce(Scrivener.SimplePage.t(), Enumerable.acc(), Enumerable.reducer()) ::
29+
Enumerable.result()
30+
def reduce(%Scrivener.SimplePage{entries: entries}, acc, fun) do
31+
Enumerable.reduce(entries, acc, fun)
32+
end
33+
34+
@spec slice(Scrivener.SimplePage.t()) :: {:error, Enumerable.Scrivener.SimplePage}
35+
def slice(_page), do: {:error, __MODULE__}
36+
end
37+
38+
defimpl Collectable do
39+
@spec into(Scrivener.SimplePage.t()) ::
40+
{term, (term, Collectable.command() -> Scrivener.SimplePage.t() | term)}
41+
def into(original) do
42+
original_entries = original.entries
43+
impl = Collectable.impl_for(original_entries)
44+
{_, entries_fun} = impl.into(original_entries)
45+
46+
fun = fn page, command ->
47+
%{page | entries: entries_fun.(page.entries, command)}
48+
end
49+
50+
{original, fun}
51+
end
52+
end
53+
end

0 commit comments

Comments
 (0)