• Stars
    star
    689
  • Rank 63,053 (Top 2 %)
  • Language
    Ruby
  • License
    MIT License
  • Created almost 11 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿ“„ Link header pagination for Rails and Grape APIs.

api-pagination

Paginate in your headers, not in your response body. This follows the proposed RFC-8288 standard for Web linking.

Installation

In your Gemfile:

# Requires Rails (Rails-API is also supported), or Grape
# v0.10.0 or later. If you're on an earlier version of
# Grape, use api-pagination v3.0.2.
gem 'rails', '>= 3.0.0'
gem 'rails-api'
gem 'grape', '>= 0.10.0'

# Then choose your preferred paginator from the following:
gem 'pagy'
gem 'kaminari'
gem 'will_paginate'

# Finally...
gem 'api-pagination'

Configuration (optional)

By default, api-pagination will detect whether you're using Pagy, Kaminari, or WillPaginate, and it will name headers appropriately. If you want to change any of the configurable settings, you may do so:

ApiPagination.configure do |config|
  # If you have more than one gem included, you can choose a paginator.
  config.paginator = :kaminari # or :will_paginate

  # By default, this is set to 'Total'
  config.total_header = 'X-Total'

  # By default, this is set to 'Per-Page'
  config.per_page_header = 'X-Per-Page'

  # Optional: set this to add a header with the current page number.
  config.page_header = 'X-Page'

  # Optional: set this to add other response format. Useful with tools that define :jsonapi format
  config.response_formats = [:json, :xml, :jsonapi]

  # Optional: what parameter should be used to set the page option
  config.page_param = :page
  # or
  config.page_param do |params|
    params[:page][:number] if params[:page].is_a?(ActionController::Parameters)
  end

  # Optional: what parameter should be used to set the per page option
  config.per_page_param = :per_page
  # or
  config.per_page_param do |params|
    params[:page][:size] if params[:page].is_a?(ActionController::Parameters)
  end

  # Optional: Include the total and last_page link header
  # By default, this is set to true
  # Note: When using kaminari, this prevents the count call to the database
  config.include_total = false
end

Pagy-specific configuration

Pagy does not have a built-in way to specify a maximum number of items per page, but api-pagination will check if you've set a :max_per_page variable. To configure this, you can use the following code somewhere in an initializer:

Pagy::DEFAULT[:max_per_page] = 100

If left unconfigured, clients can request as many items per page as they wish, so it's highly recommended that you configure this.

Rails

In your controller, provide a pageable collection to the paginate method. In its most convenient form, paginate simply mimics render:

class MoviesController < ApplicationController
  # GET /movies
  def index
    movies = Movie.all # Movie.scoped if using ActiveRecord 3.x

    paginate json: movies
  end

  # GET /movies/:id/cast
  def cast
    actors = Movie.find(params[:id]).actors

    # Override how many Actors get returned. If unspecified,
    # params[:per_page] (which defaults to 25) will be used.
    paginate json: actors, per_page: 10
  end
end

This will pull your collection from the json or xml option, paginate it for you using params[:page] and params[:per_page], render Link headers, and call ActionController::Base#render with whatever you passed to paginate. This should work well with ActiveModel::Serializers. However, if you need more control over what is done with your paginated collection, you can pass the collection directly to paginate to receive a paginated collection and have your headers set. Then, you can pass that paginated collection to a serializer or do whatever you want with it:

class MoviesController < ApplicationController
  # GET /movies
  def index
    movies = paginate Movie.all

    render json: MoviesSerializer.new(movies)
  end

  # GET /movies/:id/cast
  def cast
    actors = paginate Movie.find(params[:id]).actors, per_page: 10

    render json: ActorsSerializer.new(actors)
  end
end

Note that the collection sent to paginate must respond to your paginator's methods. This is typically fine unless you're dealing with a stock Array. For Kaminari, Kaminari.paginate_array will be called for you behind-the-scenes. For WillPaginate, you're out of luck unless you call require 'will_paginate/array' somewhere. Because this pollutes Array, it won't be done for you automatically. If you use Pagy, it doesn't matter, because Pagy doesn't care what you're paginating. It will just work, as long as the collection responds to count.

NOTE: In versions 4.4.0 and below, the Rails::Pagination module would end up included in ActionController::Base even if ActionController::API was defined. As of version 4.5.0, this is no longer the case. If for any reason your API controllers cannot easily changed be changed to inherit from ActionController::API instead, you can manually include the module:

class API::ApplicationController < ActionController::Base
  include Rails::Pagination
end

Grape

With Grape, paginate is used to declare that your endpoint takes a :page and :per_page param. You can also directly specify a :max_per_page that users aren't allowed to go over. Then, inside your API endpoint, it simply takes your collection:

class MoviesAPI < Grape::API
  format :json

  desc 'Return a paginated set of movies'
  paginate
  get do
    # This method must take an ActiveRecord::Relation
    # or some equivalent pageable set.
    paginate Movie.all
  end

  route_param :id do
    desc "Return one movie's cast, paginated"
    # Override how many Actors get returned. If unspecified,
    # params[:per_page] (which defaults to 25) will be used.
    # There is no default for `max_per_page`.
    paginate per_page: 10, max_per_page: 200
    get :cast do
      paginate Movie.find(params[:id]).actors
    end

    desc "Return one movie's awards, paginated"
    # Enforce max_per_page value will add the alowed values
    # to the swagger docs, and cause grape to return an error
    # if outside that range
    paginate per_page: 10, max_per_page: 200, enforce_max_per_page: true
    get :awards do
      paginate Movie.find(params[:id]).awards
    end
  end
end

Headers

Then curl --include to see your header-based pagination in action:

$ curl --include 'https://localhost:3000/movies?page=5'
HTTP/1.1 200 OK
Link: <http://localhost:3000/movies?page=1>; rel="first",
  <http://localhost:3000/movies?page=173>; rel="last",
  <http://localhost:3000/movies?page=6>; rel="next",
  <http://localhost:3000/movies?page=4>; rel="prev"
Total: 4321
Per-Page: 10
# ...

A Note on Kaminari and WillPaginate

api-pagination requires either Kaminari or WillPaginate in order to function, but some users may find themselves in situations where their application includes both. For example, you may have included ActiveAdmin (which uses Kaminari for pagination) and WillPaginate to do your own pagination. While it's suggested that you remove one paginator gem or the other, if you're unable to do so, you must configure api-pagination explicitly:

ApiPagination.configure do |config|
  config.paginator = :will_paginate
end

If you don't do this, an annoying warning will print once your app starts seeing traffic. You should also configure Kaminari to use a different name for its per_page method (see https://github.com/activeadmin/activeadmin/wiki/How-to-work-with-will_paginate):

Kaminari.configure do |config|
  config.page_method_name = :per_page_kaminari
end

More Repositories

1

recommendable

๐Ÿ‘๐Ÿ‘Ž A recommendation engine using Likes and Dislikes for your Ruby app
Ruby
1,351
star
2

spec-me-maybe

โ“ Introduces the `maybe` syntax to RSpec.
Ruby
167
star
3

inflections

๐Ÿ‡ช๐Ÿ‡ธ Sane and multilingual singularization/pluralization rules for ActiveSupport and Rails.
Ruby
88
star
4

goodbre.ws

๐Ÿป An example implementation of Recommendable. Unfortunately now defunct.
Ruby
59
star
5

Sunscreen

๐ŸŒ… A macOS app that sets your wallpaper based on sunrise and sunset.
Swift
58
star
6

rack-console

๐Ÿ’ป `rails console` for your Rack applications
Ruby
27
star
7

geocodio

A ruby client for the http://geocod.io/ API. Geocode with ease.
Ruby
20
star
8

dotfiles

๐ŸŸ #!/usr/bin/env fish
Shell
19
star
9

api-benchmarks

Benchmarks for various Ruby API frameworks.
Ruby
18
star
10

quiet_safari

๐Ÿ™Š If you donโ€™t care about /apple-touch-icon.png being a 404
Ruby
17
star
11

new_relic-discourse

A New Relic plugin for Discourse forums.
Ruby
17
star
12

pry-suite

A metagem to install Pry with many useful plugins.
Ruby
15
star
13

language-fish-shell

๐ŸŸ Atom snippets and syntax highlighting for Fish, the friendly interactive shell.
12
star
14

emoji

10
star
15

sparkles

โœจ Recognize teammates in slack by awarding them sparkles!
Ruby
6
star
16

vim-ariake-dark

The Ariake Dark theme for Vim
Vim Script
5
star
17

snap

A bootstrapped IRC bot using Cinch. I write plugins so you don't have to.
Ruby
5
star
18

squint-test

Do squint tests on your code in Atom!
CoffeeScript
5
star
19

davidcelis.github.io

๐Ÿ“ฐ My old homepage and blog, written with Jekyll
HTML
5
star
20

no-tco.safariextension

A Safari extension to replace t.co links with their original URL.
JavaScript
4
star
21

nook_stop_api

๐Ÿƒ Welcome to Nook Stop, a multimedia terminal from Nook Inc. This toy app is an example of how to build a GraphQL API in Ruby, powered by data from Animal Crossing: New Horizons.
HTML
4
star
22

davidcel.is

๐Ÿ“ My personal website and blog
Ruby
3
star
23

runmygi.st

Run arbitrary gists using Docker!
Ruby
3
star
24

gifs

๐ŸŽž A collection of gifs curated by and for David Celis
HTML
3
star
25

rspec-stackprof

Easily integrate stackprof with RSpec on Ruby 2.1.
Ruby
2
star
26

brewhouse

A Ruby wrapper for the BreweryDB API
Ruby
2
star
27

homebrew-spark

Ruby
1
star
28

gocean

A Go library to communicate with Digital Ocean's APIv2
1
star
29

dcelis_ebooks

๐Ÿฆ The home of my Twitter eBooks bot.
Ruby
1
star
30

my-tfe

My personal TFE setup
HCL
1
star
31

empty

This repository is empty, but it still has a commit
1
star
32

wing_clip

Sync your Foursquare check-in historyโ€”past, present, and futureโ€”to Google Calendar.
Ruby
1
star
33

SpriteSheet

A small helper class to convert .gifs to sprites.
Ruby
1
star
34

language-thrift

Atom syntax highlighting and snippets for Thrift files.
CoffeeScript
1
star
35

advent-of-code-2020

๐ŸŽ„ My solutions to 2020's Advent of Code, in Ruby
Ruby
1
star
36

puddle

WIP: A MUD server written in Node.js
JavaScript
1
star