No commit activity in last 3 years
No release in over 3 years
Provides page token based pagination for your active record
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.7
~> 10.0
>= 0
~> 1.3.10

Runtime

~> 1.2.1
~> 4.1
 Project Readme

TokenPagination

Provides page token based pagination for your active record

This module currently works together with rails > 4.1 and active record. It allows as to use pointer based queries easily and securely which would benefit in both performance wise and UI/UX wise for various cases compared to using OFFSET/LIMIT based queries.

Features

Optimized in performance

Offset limit pagination works great in lots of cases. It has two characteristics

  • Collection resource is generated every time you make a request (i.e by using query parameter)
  • Items you wish to get is specified by, starting index and count

But using offset/limit based pagination on RDBS using B-Tree can cause serious performance problem Examples below.

SELECT id FROM users LIMIT 1000000, 100

this gem paginates by selecting pointer items to paginate from, which greatly improves performance when used with Index.

SELECT id FROM users WHERE id > 1000000 LIMIT 100

Easy

You can use any active record method to filter result_set and just call page_by_token. i.e

@books = user.books.where(
                      :published => true
                    ).where(
                      "id > 100"
                    ).order(
                      :id => :desc
                    ).page_by_token(
                      count,
                      params[:page_token]
                    )

# Return jwt in string, if there is no more items to be fetched, returns nil
@next_page_token = @books.next_page_token

page_token is an JSON Web Token encoding following informations

  • Definition of collection (In this case, active_record relation object information)
  • Pointer instance specifying next item

It embedds architecture behind the pagination to single token, so we can simplify pagination queries, and make very api friendly interface as in following example.

# FIRST PAGE
GET /items?category_id=1

{
  entry: [ ... correction goes here ],
  next_page_token: "PAGE_TOKEN_"
}

# SECOND PAGE
GET /items?category_id=1&page_token=NEXT_PAGE_TOKEN_IN_PREVIOUS_REQUEST

{
  entry: [ ... correction of second page goes here ],
  next_page_token: "PAGE_TOKEN"
}

UI/UX Friendly

In some cases, offset limit pagination is unfriendly UI/UX wise. These cases are when

  • Items are rapidly pushed to top of the collection
  • Items once pushed rarely moves its location

Twitter timeline is one good example of such a collection. When you have these kind of collection, and paginate with offset/limit, pagination would likely have a problem as in following situation

  • Query first page with offset 0 limit 100
  • 10 more items are pushed
  • Query next page with offset 100 limit 100
  • 10 items are duplicated

These problems can be fixed by using pointer based pagination because, you specify items to query next page from. Any items pushed to top of the list will not affect the result of next page.

Secure

Secure, it uses JSON Web Token to be secure for malicious use of query parameter from users

@books = User.order(id::desc).page_by_token(10,"ILLEGAL_PAGE_TOKEN") # []
@books = User.order(id::desc).page_by_token!(10,"ILLEGAL_PAGE_TOKEN") # raise error

@books = User.order(id::desc).page_by_token(10,"PAGE_TOKEN_FROM_OTHER_RELATION") # []
@books = User.order(id::desc).page_by_token!(10,"PAGE_TOKEN_FROM_OTHRE_RELATION") # raise error

Installation

Add this line to your application's Gemfile:

gem 'token_pagination'

And then execute:

$ bundle

Or install it yourself as:

$ gem install token_pagination

Usage

Iterating basic collections

Getting pages are very easily implemtend, Following is an example(asuming user with 15 posts)

@posts = @user.posts.where(published:true).order(:id  => :desc).page_by_token(10)
# SELECT * FROM posts WHERE published = 1 AND user_id = ? ORDER BY id  DESC LIMT 10

# Get next page
next_page_token = @posts.next_page_token # this would be a of jwt
@posts = @user.posts.where(published: true).order(:id => :desc).page_by_token(10, next_page_token)
# SELECT * FROM posts WHERE published=1 AND user_id = ? AND id < ? LIMIT 10

next_page_token = @posts.next_page_token # nil

Ordering with multiple columns would work too

@posts = @user.posts.order(:rating  => :desc).order(:id => :desc).page_by_token(10)
# SELECT * FROM posts
# WHERE user_id = ?
# ORDER BY rating desc, created_at  DESC LIMT 10

@posts = @user.posts.order(:rating  => :desc).order(:id => :desc).page_by_token(10, @posts.page_token)
# SELECT * FROM posts
# WHERE     user_id = ?
#       AND rating <= ?
#       AND (rating < ? OR (rating = ? AND id < ?)
# ORDER BY rating desc, created_at  DESC LIMT 10

Contributing

  1. Fork it ( https://github.com/[my-github-username]/token_pagination/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request