Skip to content

NFIBrokerage/fob

Repository files navigation

Fob

Actions CI

A minimalistic keyset pagination library for Ecto queries

N.B.: you probably don't want to use Fob. Fob makes a number of assumptions about the queries passed to it, such as:

  • each schema will have only one primary key
  • each query must be ordered by at least the schema's primary key
  • the primary key is always the final ordering condition
  • associations are untested and probably do not work
    • (left) joins are known to work though

Usage

Fob works primarily by composing Ecto queries with Ecto.Query.where/3 and Ecto.Query.limit/2. In general, the tracking of page breaks is left to any consuming library/application, but Fob provides a convenience tool which it uses internally for easy testing: Fob.Cursor.

A Fob.Cursor is a similar idea to a standard elixir Stream except that it does not represent an active connection to a resource (some but not all Elixir Streams open a process or port and therefore represent a connection to resource. This disallows some functionality which is desirable for Fob, such as Stream.split/2).

With the cursor, you may walk through the pages of a dataset with Fob.Cursor.next/1 and take multiple pages with Fob.Cursor.split/2

iex> alias Fob.Cursor
iex> import Ecto.Query, only: [order_by: 2]
iex> cursor = Cursor.new(order_by(MySchema, asc: :id), MyApp.Repo, _initial_pagination = nil, _page_size = 3)
#Fob.Cursor<...>
iex> {_records, cursor} = Cursor.next(cursor)
{[%{id: 1}, %{id: 2}, %{id: 3}], #Fob.Cursor<...>}
iex> {_records, cursor} = Cursor.next(cursor)
{[%{id: 4}, %{id: 5}, %{id: 6}], #Fob.Cursor<...>}
iex> {_records, cursor} = Cursor.split(cursor, 2)
{[%{id: 7}, %{id: 8}, %{id: 9}, %{id: 10}, %{id: 11}, %{id: 12}], #Fob.Cursor<...>}

Why?

Why build an incomplete and minimal keyset pagination library when there are already workable implementations in Paginator and Quarto? Fob was originally cut out of another library called Haste. In our setup, Haste satisfies all of the above assumptions.

Installation

def deps do
  [
    {:fob, "~> 1.0"}
  ]
end

Check out the docs here: https://hexdocs.pm/fob