Module: APIHelper::Paginatable

Extended by:
ActiveSupport::Concern
Defined in:
lib/api_helper/paginatable.rb

Overview

Paginatable

Paginating the requested items can avoid returning too much data in a single response. API clients can iterate over the results with pagination instead of rerteving all the data in one time, ruining the database connection or network.

There are two available URL parameters: per_page and page. The former is used for setting how many resources will be returned in each page, there will be a maxium limit and default value for each API:

GET /posts?per_page=10

The server will respond 10 resources in a request.

Use the page parameter to specify which to page get:

GET /posts?page=5

Pagination info will be provided in the HTTP Link header like this:

Link: <http://api-server.dev/movies?page=1>; rel="first",
      <http://api-server.dev/movies?page=4>; rel="prev"
      <http://api-server.dev/movies?page=6>; rel="next",
      <http://api-server.dev/movies?page=238>; rel="last"

Line breaks are added for readability.

Which follows the proposed RFC 5988 standard.

An aditional header, X-Items-Count, will also be set to the total pages count.

Usage

Include this Concern in your Action Controller:

SamplesController < ApplicationController
  include APIHelper::Paginatable
end

or in your Grape API class:

class SampleAPI < Grape::API
  helpers APIHelper::Paginatable
end

then set the options for pagination in the grape method, as the following as an example:

resources :posts do
  get do
    collection = current_user.posts
    pagination collection.count, default_per_page: 25, maxium_per_page: 100

    # ...
  end
end

Then use the helper methods like this:

# this example uses kaminari
User.page(pagination_per_page).per(pagination_page)

HTTP Link header will be automatically set by the way.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.page_param_descObject

Return the ‘page’ param description



196
197
198
# File 'lib/api_helper/paginatable.rb', line 196

def self.page_param_desc
  "Specify which page you want to get."
end

.per_page_param_descObject

Return the ‘per_page’ param description



191
192
193
# File 'lib/api_helper/paginatable.rb', line 191

def self.per_page_param_desc
  "Specify how many items you want each page to return."
end

Instance Method Details

#add_or_replace_uri_param(url, param_name, param_value) ⇒ Object

:nodoc:



181
182
183
184
185
186
187
188
# File 'lib/api_helper/paginatable.rb', line 181

def add_or_replace_uri_param(url, param_name, param_value) # :nodoc:
  uri = URI(url)
  params = URI.decode_www_form(uri.query || '')
  params.delete_if { |param| param[0].to_s == param_name.to_s }
  params << [param_name, param_value]
  uri.query = URI.encode_www_form(params)
  uri.to_s
end

#pagination(items_count, default_per_page: 20, maxium_per_page: 100, set_header: true) ⇒ Object

Set pagination for the request

Params:

items_count

Symbol name of resource to receive the inclusion

default_per_page

Integer default per_page

maxium_per_page

Integer maximum results do return on a single page



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/api_helper/paginatable.rb', line 84

def pagination(items_count, default_per_page: 20,
                            maxium_per_page: 100,
                            set_header: true)
  items_count = items_count.count if items_count.respond_to? :count

  @pagination_per_page = (params[:per_page] || default_per_page).to_i
  @pagination_per_page = maxium_per_page if @pagination_per_page > maxium_per_page
  @pagination_per_page = 1 if @pagination_per_page < 1

  items_count = 0 if items_count < 0
  pages_count = (items_count.to_f / @pagination_per_page).ceil
  pages_count = 1 if pages_count < 1

  @pagination_items_count = items_count
  @pagination_pages_count = pages_count

  @pagination_page = (params[:page] || 1).to_i
  @pagination_page = pages_count if @pagination_page > pages_count
  @pagination_page = 1 if @pagination_page < 1

  if current_page > 1
    @pagination_first_page_url = add_or_replace_uri_param(request.url, :page, 1)
    @pagination_prev_page_url = add_or_replace_uri_param(request.url, :page, (current_page > pages_count ? pages_count : current_page - 1))
  end

  if current_page < pages_count
    @pagination_next_page_url = add_or_replace_uri_param(request.url, :page, current_page + 1)
    @pagination_last_page_url = add_or_replace_uri_param(request.url, :page, pages_count)
  end

  if set_header
    link_headers ||= []

    if current_page > 1
      link_headers << "<#{@pagination_first_page_url}>; rel=\"first\"" if @pagination_first_page_url
      link_headers << "<#{@pagination_prev_page_url}>; rel=\"prev\"" if @pagination_prev_page_url
    end

    if current_page < pages_count
      link_headers << "<#{@pagination_next_page_url}>; rel=\"next\"" if @pagination_next_page_url
      link_headers << "<#{@pagination_last_page_url}>; rel=\"last\"" if @pagination_last_page_url
    end

    link_header = link_headers.join(', ')

    if self.respond_to?(:header)
      self.header('Link', link_header)
      self.header('X-Items-Count', items_count.to_s)
      self.header('X-Pages-Count', pages_count.to_s)
    end

    if defined?(response) && response.respond_to?(:headers)
      response.headers['Link'] = link_header
      response.headers['X-Items-Count'] = items_count.to_s
      response.headers['X-Pages-Count'] = pages_count.to_s
    end
  end
end

#pagination_first_page_urlObject



163
164
165
# File 'lib/api_helper/paginatable.rb', line 163

def pagination_first_page_url
  @pagination_first_page_url
end

#pagination_items_countObject



155
156
157
# File 'lib/api_helper/paginatable.rb', line 155

def pagination_items_count
  @pagination_items_count
end

#pagination_last_page_urlObject



175
176
177
# File 'lib/api_helper/paginatable.rb', line 175

def pagination_last_page_url
  @pagination_last_page_url
end

#pagination_next_page_urlObject



171
172
173
# File 'lib/api_helper/paginatable.rb', line 171

def pagination_next_page_url
  @pagination_next_page_url
end

#pagination_pageObject Also known as: current_page

Getter for the current page



144
145
146
# File 'lib/api_helper/paginatable.rb', line 144

def pagination_page
  @pagination_page
end

#pagination_pages_countObject



159
160
161
# File 'lib/api_helper/paginatable.rb', line 159

def pagination_pages_count
  @pagination_pages_count
end

#pagination_per_pageObject Also known as: paginate_with

Getter for per_page



151
152
153
# File 'lib/api_helper/paginatable.rb', line 151

def pagination_per_page
  @pagination_per_page
end

#pagination_prev_page_urlObject



167
168
169
# File 'lib/api_helper/paginatable.rb', line 167

def pagination_prev_page_url
  @pagination_prev_page_url
end