Rumamge.Ecto v1.3.0-rc.0 Rummage.Ecto.CustomHooks.KeysetPaginate View Source

Rummage.Ecto.CustomHooks.KeysetPaginate is an example of a Custom Hook that comes with Rummage.Ecto.

This module uses keyset pagination to add a pagination query expression on top a given Ecto.Queryable.

For more information on Keyset Pagination, check this article

NOTE: This module doesn’t return a list of entries, but a Ecto.Query.t. This module uses Rummage.Ecto.Hook.


ABOUT:

Arguments:

This Hook expects a queryable (an Ecto.Queryable) and paginate_params (a Map). The map should be in the format: %{per_page: 10, page: 1, last_seen_pk: 10, pk: :id}

Details:

  • per_page: Specifies the entries in each page.
  • page: Specifies the page number.
  • last_seen_pk: Specifies the primary_key value of last_seen entry, This hook uses this entry instead of offset.
  • pk: Specifies what’s the primary_key for the entries being paginated. Cannot be nil

For example, if we want to paginate products (primary_key = :id), we would do the following:

Rummage.Ecto.CustomHooks.KeysetPaginate.run(Product,
  %{per_page: 10, page: 1, last_seen_pk: 10, pk: :id})

When to Use KeysetPaginate?

  • Keyset Pagination is mainly here to make pagination faster for complex pages. It is recommended that you use Rummage.Ecto.Hooks.Paginate for a simple pagination operation, as this module has a lot of assumptions and it’s own ordering on top of the given query.

NOTE: It is not recommended to use this with the native sort hook


ASSUMPTIONS/NOTES:

  • This Hook assumes that the querried Ecto.Schema has a primary_key.
  • This Hook also orders the query by ascending primary_key

USAGE

alias Rummage.Ecto.CustomHooks.KeysetPaginate

queryable = KeysetPaginate.run(Parent,
  %{per_page: 10, page: 1, last_seen_pk: 10, pk: :id})

This module can be used by overriding the default module. This can be done in the following ways:

In the Rummage.Ecto call:

Rummage.Ecto.rummage(queryable, rummage,
  paginate: Rummage.Ecto.CustomHooks.KeysetPaginate)

OR

Globally for all models in config.exs:

config :my_app,
  Rummage.Ecto,
  paginate: Rummage.Ecto.CustomHooks.KeysetPaginate

OR

When using Rummage.Ecto with an Ecto.Schema:

defmodule MySchema do
  use Rummage.Ecto, repo: SomeRepo,
    paginate: Rummage.Ecto.CustomHooks.KeysetPaginate
end

Link to this section Summary

Functions

Callback implementation for Rummage.Ecto.Hook.format_params/2

This is the callback implementation of Rummage.Ecto.Hook.run/2

Link to this section Functions

Link to this function format_params(queryable, params, opts) View Source
format_params(Ecto.Query.t(), map(), keyword()) :: map()
format_params(Ecto.Query.t(), map(), keyword()) :: map()

Callback implementation for Rummage.Ecto.Hook.format_params/2.

This function takes an Ecto.Query.t or queryable, paginate_params which will be passed to the run/2 function, but also takes a list of options, opts.

The function expects opts to include a repo key which points to the Ecto.Repo which will be used to calculate the total_count and max_page for this paginate hook module.

Examples

When a repo isn’t passed in opts it gives an error:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> alias Rummage.Ecto.Category
iex> KeysetPaginate.format_params(Category, %{per_page: 1, page: 1}, [])
** (RuntimeError) Expected key `repo` in `opts`, got []

When paginate_params given aren’t valid, it uses defaults to populate params:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> alias Rummage.Ecto.Category
iex> Ecto.Adapters.SQL.Sandbox.checkout(Rummage.Ecto.Repo)
iex> KeysetPaginate.format_params(Category, %{}, [repo: Rummage.Ecto.Repo])
%{max_page: 0, page: 1, per_page: 10, total_count: 0, pk: :id,
  last_seen_pk: 0}

When paginate_params and opts given are valid:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> alias Rummage.Ecto.Category
iex> paginate_params = %{
...>   per_page: 1,
...>   page: 1
...> }
iex> repo = Rummage.Ecto.Repo
iex> Ecto.Adapters.SQL.Sandbox.checkout(repo)
iex> KeysetPaginate.format_params(Category, paginate_params, [repo: repo])
%{max_page: 0, last_seen_pk: 0, page: 1,
  per_page: 1, total_count: 0, pk: :id}

When paginate_params and opts given are valid:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> alias Rummage.Ecto.Category
iex> paginate_params = %{
...>   per_page: 1,
...>   page: 1
...> }
iex> repo = Rummage.Ecto.Repo
iex> Ecto.Adapters.SQL.Sandbox.checkout(repo)
iex> repo.insert!(%Category{category_name: "name"})
iex> repo.insert!(%Category{category_name: "name2"})
iex> KeysetPaginate.format_params(Category, paginate_params, [repo: repo])
%{max_page: 2, last_seen_pk: 0, page: 1,
  per_page: 1, total_count: 2, pk: :id}

When paginate_params and opts given are valid and when the queryable passed has a primary_key defaulted to id.

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> alias Rummage.Ecto.Category
iex> paginate_params = %{
...>   per_page: 1,
...>   page: 1
...> }
iex> repo = Rummage.Ecto.Repo
iex> Ecto.Adapters.SQL.Sandbox.checkout(repo)
iex> repo.insert!(%Category{category_name: "name"})
iex> repo.insert!(%Category{category_name: "name2"})
iex> KeysetPaginate.format_params(Category, paginate_params, [repo: repo])
%{max_page: 2, last_seen_pk: 0, page: 1,
  per_page: 1, total_count: 2, pk: :id}

When paginate_params and opts given are valid and when the queryable passed has a custom primary_key.

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> alias Rummage.Ecto.Item
iex> paginate_params = %{
...>   per_page: 1,
...>   page: 2
...> }
iex> repo = Rummage.Ecto.Repo
iex> Ecto.Adapters.SQL.Sandbox.checkout(repo)
iex> repo.insert!(%Item{item_id: 5})
iex> repo.insert!(%Item{item_id: 6})
iex> KeysetPaginate.format_params(Item, paginate_params, [repo: repo])
%{max_page: 2, last_seen_pk: 1, page: 2,
  per_page: 1, total_count: 2, pk: :item_id}
Link to this function run(queryable, params) View Source
run(Ecto.Query.t(), map()) :: Ecto.Query.t()
run(Ecto.Query.t(), map()) :: Ecto.Query.t()

This is the callback implementation of Rummage.Ecto.Hook.run/2.

Builds a paginate Ecto.Query.t on top of a given Ecto.Query.t variable with given params.

Besides an Ecto.Query.t an Ecto.Schema module can also be passed as it implements Ecto.Queryable

Params is a Map which is expected to have the keys per_page, page, last_seen_pk, pk.

If an expected key isn’t given, a Runtime Error is raised.

Examples

When an empty map is passed as params:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> KeysetPaginate.run(Parent, %{})
** (RuntimeError) Error in params, No values given for keys: per_page, page, last_seen_pk, pk

When a non-empty map is passed as params, but with a missing key:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> KeysetPaginate.run(Parent, %{per_page: 10})
** (RuntimeError) Error in params, No values given for keys: page, last_seen_pk, pk

When a valid map of params is passed with an Ecto.Schema module:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> params = %{per_page: 10, page: 1, last_seen_pk: 0, pk: :id}
iex> KeysetPaginate.run(Rummage.Ecto.Product, params)
#Ecto.Query<from p in Rummage.Ecto.Product, where: p.id > ^0, limit: ^10>

When the queryable passed is an Ecto.Query variable:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> import Ecto.Query
iex> queryable = from u in "products"
#Ecto.Query<from p in "products">
iex> params = %{per_page: 10, page: 1, last_seen_pk: 0, pk: :id}
iex> KeysetPaginate.run(queryable, params)
#Ecto.Query<from p in "products", where: p.id > ^0, limit: ^10>

More examples:

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> import Ecto.Query
iex> params = %{per_page: 5, page: 5, last_seen_pk: 25, pk: :id}
iex> queryable = from u in "products"
#Ecto.Query<from p in "products">
iex> KeysetPaginate.run(queryable, params)
#Ecto.Query<from p in "products", where: p.id > ^25, limit: ^5>

iex> alias Rummage.Ecto.CustomHooks.KeysetPaginate
iex> import Ecto.Query
iex> params = %{per_page: 5, page: 1, last_seen_pk: 0, pk: :some_id}
iex> queryable = from u in "products"
#Ecto.Query<from p in "products">
iex> KeysetPaginate.run(queryable, params)
#Ecto.Query<from p in "products", where: p.some_id > ^0, limit: ^5>