Paginator behaviour (Paginator v1.0.4) View Source

Defines a paginator.

This module adds a paginate/3 function to your Ecto.Repo so that you can paginate through results using opaque cursors.

Usage

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app
  use Paginator
end

Options

Paginator can take any options accepted by paginate/3. This is useful when you want to enforce some options globally across your project.

Example

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app
  use Paginator,
    limit: 10,                           # sets the default limit to 10
    maximum_limit: 100,                  # sets the maximum limit to 100
    include_total_count: true,           # include total count by default
    total_count_primary_key_field: :uuid # sets the total_count_primary_key_field to uuid for calculate total_count
end

Note that these values can be still be overriden when paginate/3 is called.

Use without macros

If you wish to avoid use of macros or you wish to use a different name for the pagination function you can define your own function like so:

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app

  def my_paginate_function(queryable, opts \ [], repo_opts \ []) do
    defaults = [limit: 10] # Default options of your choice here
    opts = Keyword.merge(defaults, opts)
    Paginator.paginate(queryable, opts, __MODULE__, repo_opts)
  end
end

Link to this section Summary

Functions

Generate a cursor for the supplied record, in the same manner as the before and after cursors generated by paginate/3.

Default function used to get the value of a cursor field from the supplied map. This function can be overriden in the Paginator.Config using the fetch_cursor_value_fun key.

Callbacks

Fetches all the results matching the query within the cursors.

Link to this section Functions

Link to this function

cursor_for_record(record, cursor_fields, fetch_cursor_value_fun \\ &Paginator.default_fetch_cursor_value/2)

View Source

Specs

cursor_for_record(any(), [atom()], (map(), atom() | {atom(), atom()} -> any())) ::
  binary()

Generate a cursor for the supplied record, in the same manner as the before and after cursors generated by paginate/3.

For the cursor to be compatible with paginate/3, cursor_fields must have the same value as the cursor_fields option passed to it.

Example

iex> Paginator.cursor_for_record(%Paginator.Customer{id: 1}, [:id])
"g3QAAAABZAACaWRhAQ=="

iex> Paginator.cursor_for_record(%Paginator.Customer{id: 1, name: "Alice"}, [id: :asc, name: :desc])
"g3QAAAACZAACaWRhAWQABG5hbWVtAAAABUFsaWNl"
Link to this function

default_fetch_cursor_value(schema, field)

View Source

Specs

default_fetch_cursor_value(map(), atom() | {atom(), atom()}) :: any()

Default function used to get the value of a cursor field from the supplied map. This function can be overriden in the Paginator.Config using the fetch_cursor_value_fun key.

When using named bindings to sort on joined columns it will attempt to get the value of joined column by using the named binding as the name of the relationship on the original Ecto.Schema.

Example

iex> Paginator.default_fetch_cursor_value(%Paginator.Customer{id: 1}, :id)
1

iex> Paginator.default_fetch_cursor_value(%Paginator.Customer{id: 1, address: %Paginator.Address{city: "London"}}, {:address, :city})
"London"

Link to this section Callbacks

Link to this callback

paginate(queryable, opts, repo_opts)

View Source

Specs

paginate(
  queryable :: Ecto.Query.t(),
  opts :: Keyword.t(),
  repo_opts :: Keyword.t()
) :: Paginator.Page.t()

Fetches all the results matching the query within the cursors.

Options

  • :after - Fetch the records after this cursor.
  • :before - Fetch the records before this cursor.
  • :cursor_fields - The fields with sorting direction used to determine the cursor. In most cases, this should be the same fields as the ones used for sorting in the query. When you use named bindings in your query they can also be provided.
  • :fetch_cursor_value_fun function of arity 2 to lookup cursor values on returned records. Defaults to Paginator.default_fetch_cursor_value/2
  • :include_total_count - Set this to true to return the total number of records matching the query. Note that this number will be capped by :total_count_limit. Defaults to false.
  • :total_count_primary_key_field - Running count queries on specified column of the table
  • :limit - Limits the number of records returned per page. Note that this number will be capped by :maximum_limit. Defaults to 50.
  • :maximum_limit - Sets a maximum cap for :limit. This option can be useful when :limit is set dynamically (e.g from a URL param set by a user) but you still want to enfore a maximum. Defaults to 500.
  • :sort_direction - The direction used for sorting. Defaults to :asc. It is preferred to set the sorting direction per field in :cursor_fields.
  • :total_count_limit - Running count queries on tables with a large number of records is expensive so it is capped by default. Can be set to :infinity in order to count all the records. Defaults to 10,000.

Repo options

This will be passed directly to Ecto.Repo.all/2, as such any option supported by this function can be used here.

Simple example

query = from(p in Post, order_by: [asc: p.inserted_at, asc: p.id], select: p)

Repo.paginate(query, cursor_fields: [:inserted_at, :id], limit: 50)

Example with using custom sort directions per field

query = from(p in Post, order_by: [asc: p.inserted_at, desc: p.id], select: p)

Repo.paginate(query, cursor_fields: [inserted_at: :asc, id: :desc], limit: 50)

Example with sorting on columns in joined tables

from(
  p in Post,
  as: :posts,
  join: a in assoc(p, :author),
  as: :author,
  preload: [author: a],
  select: p,
  order_by: [
    {:asc, a.name},
    {:asc, p.id}
  ]
)

Repo.paginate(query, cursor_fields: [{{:author, :name}, :asc}, id: :asc], limit: 50)

When sorting on columns in joined tables it is necessary to use named bindings. In this case we name it author. In the cursor_fields we refer to this named binding and its column name.

To build the cursor Paginator uses the returned Ecto.Schema. When using a joined column the returned Ecto.Schema won't have the value of the joined column unless we preload it. E.g. in this case the cursor will be build up from post.id and post.author.name. This presupposes that the named of the binding is the same as the name of the relationship on the original struct.

One level deep joins are supported out of the box but if we join on a second level, e.g. post.author.company.name a custom function can be supplied to handle the cursor value retrieval. This also applies when the named binding does not map to the name of the relationship.

Example

from(
  p in Post,
  as: :posts,
  join: a in assoc(p, :author),
  as: :author,
  join: c in assoc(a, :company),
  as: :company,
  preload: [author: a],
  select: p,
  order_by: [
    {:asc, a.name},
    {:asc, p.id}
  ]
)

Repo.paginate(query,
  cursor_fields: [{{:company, :name}, :asc}, id: :asc],
  fetch_cursor_value_fun: fn
    post, {{:company, name}, _} ->
      post.author.company.name

    post, field ->
      Paginator.default_fetch_cursor_value(post, field)
  end,
  limit: 50
)