Packages

A collection of utilities and helpers for Phoenix and Elixir projects.

Current section

Files

Jump to
phutilx lib database paginate.ex
Raw

lib/database/paginate.ex

defmodule Phutilx.Database.Paginate do
defstruct values: nil, page: 1, size: 10, count: 0
@type t :: %__MODULE__{
values: list(),
page: integer(),
size: integer(),
count: integer()
}
@spec paginate(Ecto.Repo.t(), Ecto.Query.t(), %{page: integer(), size: integer()}) ::
Phutilx.Database.Paginate.t()
@moduledoc """
Helps with creating filters on resources in the database by creating a helper macros and function and by
consuming the module with use, it will also import the ecto modules needed to create filter schemas and
importing ecto query functions.
"""
import Ecto.Query
@doc """
A pagination macro that adds the fields `:size` and `:page` to the embedded schema. The default
values for these fields can be configured via options.
## Options
* `:default_page` - The default page number (default: `1`)
* `:default_size` - The default page size (default: `25`)
* `:max_size` - The maximum page size allowed (default: `100`)
"""
defmacro paginate(opts \\ []) do
default_page = Keyword.get(opts, :default_page, 1)
default_size = Keyword.get(opts, :default_size, 25)
max_size = Keyword.get(opts, :max_size, 100)
quote do
field(:page, :integer, default: unquote(default_page))
field(:size, :integer, default: unquote(default_size))
@pagination_options %{
page: unquote(default_page),
size: unquote(default_size),
max_size: unquote(max_size)
}
end
end
defmacro __using__(_opts) do
quote do
import Phutilx.Database.Paginate
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
Module.register_attribute(__MODULE__, :pagination_options, accumulate: false)
@before_compile Phutilx.Database.Paginate
def validate(attrs) do
%__MODULE__{}
|> changeset(attrs)
|> apply_action(:validate)
end
end
end
defmacro __before_compile__(_env) do
quote do
@doc """
Validates the pagination fields `:page` and `:size` in the given changeset. The function uses the
pagination options defined in the module attribute `@pagination_options` to validate the fields.
You do not need to pass the `:page` and `:size` fields to the changeset, they are extracted from the `attrs`,
so you also do not have to pass them to the `cast/3` function in your changeset function.
## Examples
iex> validate_pagination(%Ecto.Changeset{}, %{"page" => 2, "size" => 50})
#Ecto.Changeset<...>
iex> validate_pagination(%Ecto.Changeset{}, %{"page" => 0, "size" => 150})
#Ecto.Changeset<...>
"""
def validate_pagination(changeset, attrs) do
%__MODULE__{}
|> cast(attrs, [:page, :size])
|> validate_number(:page, greater_than: 0)
|> validate_number(:size,
greater_than: 0,
less_than_or_equal_to: @pagination_options[:max_size]
)
|> merge(changeset)
end
end
end
def paginate(repo, query, %{page: page, size: size}) do
values =
query
|> limit(^size)
|> offset((^page - 1) * ^size)
|> repo.all()
count = query |> repo.aggregate(:count, :id)
%__MODULE__{
values: values,
page: page,
size: size,
count: count
}
end
end