All Projects → duffelhq → Paginator

duffelhq / Paginator

Licence: mit
Cursor-based pagination for Elixir Ecto

Programming Languages

elixir
2628 projects

Projects that are alternatives of or similar to Paginator

comeonin ecto password
Ecto type for saving encrypted passwords using Comeonin
Stars: ✭ 34 (-90.91%)
Mutual labels:  ecto
one plus n detector
Elixir library to help you detect 1+n queries in applications using Ecto
Stars: ✭ 20 (-94.65%)
Mutual labels:  ecto
Mongodb ecto
MongoDB adapter for Ecto
Stars: ✭ 318 (-14.97%)
Mutual labels:  ecto
commanded-ecto-projections
Read model projections for Commanded using Ecto
Stars: ✭ 68 (-81.82%)
Mutual labels:  ecto
ecto diff
Generates a data structure describing the difference between two ecto structs
Stars: ✭ 22 (-94.12%)
Mutual labels:  ecto
markee
Visual text selection
Stars: ✭ 22 (-94.12%)
Mutual labels:  cursor
macOS-cursors-for-Windows
Tested in Windows 10 & 11, 4K, 125%, 150%, 200%. With 2 versions, 2 types and 3 different sizes!
Stars: ✭ 578 (+54.55%)
Mutual labels:  cursor
Machinery
State machine thin layer for structs (+ GUI for Phoenix apps)
Stars: ✭ 367 (-1.87%)
Mutual labels:  ecto
ecto autoslug field
Automatically create slugs for Ecto schemas.
Stars: ✭ 138 (-63.1%)
Mutual labels:  ecto
Mongoose Paginate V2
A cursor based custom pagination library for Mongoose with customizable labels.
Stars: ✭ 283 (-24.33%)
Mutual labels:  cursor
multi-cursor
🎉
Stars: ✭ 44 (-88.24%)
Mutual labels:  cursor
flop
Filtering, ordering and pagination for Ecto
Stars: ✭ 56 (-85.03%)
Mutual labels:  ecto
ng-caret-aware
AngularJS directive for caret aware elements
Stars: ✭ 12 (-96.79%)
Mutual labels:  cursor
fastener
Functional Zipper for manipulating JSON
Stars: ✭ 54 (-85.56%)
Mutual labels:  cursor
React Native Input Scroll View
Perfect TextInput ScrollView
Stars: ✭ 323 (-13.64%)
Mutual labels:  cursor
pretty print formatter
Pretty Print Formatter for Elixir Logger module -- Colorize Ecto's SQL ouput 🖌️
Stars: ✭ 22 (-94.12%)
Mutual labels:  ecto
mongoose-graphql-pagination
GraphQL cursor pagination (Relay-like) for Mongoose models.
Stars: ✭ 29 (-92.25%)
Mutual labels:  cursor
Sideways.vim
A Vim plugin to move function arguments (and other delimited-by-something items) left and right.
Stars: ✭ 370 (-1.07%)
Mutual labels:  cursor
Crecto
Database wrapper and ORM for Crystal, inspired by Ecto
Stars: ✭ 325 (-13.1%)
Mutual labels:  ecto
Triplex
Database multitenancy for Elixir applications!
Stars: ✭ 261 (-30.21%)
Mutual labels:  ecto

Paginator

Build Status Inline docs

Cursor based pagination for Elixir Ecto.

Documentation

Why?

There are several ways to implement pagination in a project and they all have pros and cons depending on your situation.

Limit-offset

This is the easiest method to use and implement: you just have to set LIMIT and OFFSET on your queries and the database will return records based on this two parameters. Unfortunately, it has two major drawbacks:

  • Inconsistent results: if the dataset changes while you are querying, the results in the page will shift and your user might end seeing records they have already seen and missing new ones.

  • Inefficiency: OFFSET N instructs the database to skip the first N results of a query. However, the database must still fetch these rows from disk and order them before it can returns the ones requested. If the dataset you are querying is large this will result in significant slowdowns.

Cursor-based (a.k.a keyset pagination)

This method relies on opaque cursor to figure out where to start selecting records. It is more performant than LIMIT-OFFSET because it can filter records without traversing all of them.

It's also consistent, any insertions/deletions before the current page will leave results unaffected.

It has some limitations though: for instance you can't jump directly to a specific page. This may not be an issue for an API or if you use infinite scrolling on your website.

Learn more

Getting started

defmodule MyApp.Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres

  use Paginator
end

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

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

# `page.entries` contains all the entries for this page.
# `page.metadata` contains the metadata associated with this page (cursors, limit, total count)

Install

Add paginator to your list of dependencies in mix.exs:

def deps do
  [{:paginator, "~> 1.0.4"}]
end

Usage

  1. Add Paginator to your repo.

    defmodule MyApp.Repo do
      use Ecto.Repo,
        otp_app: :my_app,
        adapter: Ecto.Adapters.Postgres
    
      use Paginator
    end
    
  2. Use the paginate function to paginate your queries.

    query = from(p in Post, order_by: [asc: p.inserted_at, asc: p.id])
    
    # return the first 50 posts
    %{entries: entries, metadata: metadata} = Repo.paginate(query, cursor_fields: [:inserted_at, :id], limit: 50)
    
    # assign the `after` cursor to a variable
    cursor_after = metadata.after
    
    # return the next 50 posts
    %{entries: entries, metadata: metadata} = Repo.paginate(query, after: cursor_after, cursor_fields: [{:inserted_at, :asc}, {:id, :asc}], limit: 50)
    
    # assign the `before` cursor to a variable
    cursor_before = metadata.before
    
    # return the previous 50 posts (if no post was created in between it should be the same list as in our first call to `paginate`)
    %{entries: entries, metadata: metadata} = Repo.paginate(query, before: cursor_before, cursor_fields: [:inserted_at, :id], limit: 50)
    
    # return total count
    # NOTE: this will issue a separate `SELECT COUNT(*) FROM table` query to the database.
    %{entries: entries, metadata: metadata} = Repo.paginate(query, include_total_count: true, cursor_fields: [:inserted_at, :id], limit: 50)
    
    IO.puts "total count: #{metadata.total_count}"
    

Security Considerations

Repo.paginate/4 will throw an ArgumentError should it detect an executable term in the cursor parameters passed to it (before,after`). This is done to protect you from potential side-effects of malicious user input, see paginator_test.exs.

Indexes

If you want to reap all the benefits of this method it is better that you create indexes on the columns you are using as cursor fields.

Example

# If your cursor fields are: [:inserted_at, :id]
# Add the following in a migration

create index("posts", [:inserted_at, :id])

Caveats

  • This method requires a deterministic sort order. If the columns you are currently using for sorting don't match that definition, just add any unique column and extend your index accordingly.
  • You need to add order_by clauses yourself before passing your query to paginate/2. In the future we might do that for you automatically based on the fields specified in :cursor_fields.
  • There is an outstanding issue where Postgrex fails to properly builds the query if it includes custom PostgreSQL types.
  • This library has only be tested with PostgreSQL.

Documentation

Documentation is written into the library, you will find it in the source code, accessible from iex and of course, it all gets published to hexdocs.

Contributing

Running tests

Clone the repo and fetch its dependencies:

$ git clone https://github.com/duffelhq/paginator.git
$ cd paginator
$ mix deps.get
$ mix test

Building docs

$ mix docs

LICENSE

See LICENSE

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].