Skip to content

Commit

Permalink
Allow configuration overrides from request options
Browse files Browse the repository at this point in the history
  • Loading branch information
StefSchenkelaars committed Jul 10, 2020
1 parent df05199 commit 5eee138
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 41 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ class MoviesController < ApplicationController
# params[:per_page] (which defaults to 25) will be used.
paginate json: actors, per_page: 10
end

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

# Override any configuration setting on request basis.
# For example you may want to disable the total count since the count query is slow.
paginate json: actors, total_count: false
end
end
```

Expand All @@ -117,6 +126,13 @@ class MoviesController < ApplicationController

render json: ActorsSerializer.new(actors)
end

# GET /movies/:id/reviews
def reviews
reviews = paginate Movie.find(params[:id]).reviews, total_count: false

render json: ReviewSerializer.new(reviews)
end
end
```

Expand Down
31 changes: 17 additions & 14 deletions lib/api-pagination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,43 @@
module ApiPagination
class << self
def paginate(collection, options = {})
options[:page] = options[:page].to_i
options[:page] = 1 if options[:page] <= 0
options[:per_page] = options[:per_page].to_i
options[:page] = options[:page].to_i
options[:page] = 1 if options[:page] <= 0
options[:per_page] = options[:per_page].to_i
options[:paginator] ||= ApiPagination.config.paginator

case ApiPagination.config.paginator
case options[:paginator]
when :pagy
paginate_with_pagy(collection, options)
when :kaminari
paginate_with_kaminari(collection, options, options[:paginate_array_options] || {})
when :will_paginate
paginate_with_will_paginate(collection, options)
else
raise StandardError, "Unknown paginator: #{ApiPagination.config.paginator}"
raise StandardError, "Unknown paginator: #{options[:paginator]}"
end
end

def pages_from(collection, options = {})
return pagy_pages_from(collection) if ApiPagination.config.paginator == :pagy && collection.is_a?(Pagy)
options[:paginator] ||= ApiPagination.config.paginator
return pagy_pages_from(collection, options) if options[:paginator] == :pagy && collection.is_a?(Pagy)

{}.tap do |pages|
unless collection.first_page?
pages[:first] = 1
pages[:prev] = collection.current_page - 1
end

unless collection.last_page? || (ApiPagination.config.paginator == :kaminari && collection.out_of_range?)
pages[:last] = collection.total_pages if ApiPagination.config.include_total
unless collection.last_page? || (options[:paginator] == :kaminari && collection.out_of_range?)
pages[:last] = collection.total_pages if options[:include_total]
pages[:next] = collection.current_page + 1
end
end
end

def total_from(collection)
case ApiPagination.config.paginator
def total_from(collection, options)
options[:paginator] ||= ApiPagination.config.paginator
case options[:paginator]
when :pagy then collection.count.to_s
when :kaminari then collection.total_count.to_s
when :will_paginate then collection.total_entries.to_s
Expand Down Expand Up @@ -69,19 +72,19 @@ def pagy_from(collection, options)
else
count = collection.is_a?(Array) ? collection.count : collection.count(:all)
end

Pagy.new(count: count, items: options[:per_page], page: options[:page])
end

def pagy_pages_from(pagy)
def pagy_pages_from(pagy, options)
{}.tap do |pages|
unless pagy.page == 1
pages[:first] = 1
pages[:prev] = pagy.prev
end

unless pagy.page == pagy.pages
pages[:last] = pagy.pages if ApiPagination.config.include_total
pages[:last] = pagy.pages if options[:include_total]
pages[:next] = pagy.next
end
end
Expand All @@ -96,7 +99,7 @@ def paginate_with_kaminari(collection, options, paginate_array_options = {})

collection = Kaminari.paginate_array(collection, paginate_array_options) if collection.is_a?(Array)
collection = collection.page(options[:page]).per(options[:per_page])
collection.without_count if !collection.is_a?(Array) && !ApiPagination.config.include_total
collection.without_count if !collection.is_a?(Array) && !options[:include_total]
[collection, nil]
end

Expand Down
32 changes: 17 additions & 15 deletions lib/grape/pagination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ module Grape
module Pagination
def self.included(base)
Grape::Endpoint.class_eval do
def paginate(collection)
per_page = ApiPagination.config.per_page_param(params) || route_setting(:per_page)

options = {
:page => ApiPagination.config.page_param(params),
:per_page => [per_page, route_setting(:max_per_page)].compact.min
def paginate(collection, options = {})
per_page = ApiPagination.config.per_page_param(params) || route_setting(:per_page)
options[:per_page] = [per_page, route_setting(:max_per_page)].compact.min
options[:page] = ApiPagination.config.page_param(params)

default_options = {
:total_header => ApiPagination.config.total_header,
:per_page_header => ApiPagination.config.per_page_header,
:page_header => ApiPagination.config.page_header,
:include_total => ApiPagination.config.include_total,
:paginator => ApiPagination.config.paginator
}
options.reverse_merge!(default_options)

collection, pagy = ApiPagination.paginate(collection, options)

links = (header['Link'] || "").split(',').map(&:strip)
Expand All @@ -21,15 +28,10 @@ def paginate(collection)
links << %(<#{url}?#{new_params.to_param}>; rel="#{k}")
end

total_header = ApiPagination.config.total_header
per_page_header = ApiPagination.config.per_page_header
page_header = ApiPagination.config.page_header
include_total = ApiPagination.config.include_total

header 'Link', links.join(', ') unless links.empty?
header total_header, ApiPagination.total_from(pagy || collection).to_s if include_total
header per_page_header, options[:per_page].to_s
header page_header, options[:page].to_s unless page_header.nil?
header 'Link', links.join(', ') unless links.empty?
header options[:total_header], ApiPagination.total_from(pagy || collection, options).to_s if options[:include_total]
header options[:per_page_header], options[:per_page].to_s
header options[:page_header], options[:page].to_s unless options[:page_header].nil?

return collection
end
Expand Down
25 changes: 14 additions & 11 deletions lib/rails/pagination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ def _discover_format(options)

def _paginate_collection(collection, options={})
options[:page] = ApiPagination.config.page_param(params)
options[:per_page] ||= ApiPagination.config.per_page_param(params)
default_options = {
:per_page => ApiPagination.config.per_page_param(params),
:total_header => ApiPagination.config.total_header,
:per_page_header => ApiPagination.config.per_page_header,
:page_header => ApiPagination.config.page_header,
:include_total => ApiPagination.config.include_total,
:paginator => ApiPagination.config.paginator
}
options.reverse_merge!(default_options)

collection, pagy = ApiPagination.paginate(collection, options)

Expand All @@ -45,25 +53,20 @@ def _paginate_collection(collection, options={})
links << %(<#{url}?#{new_params.to_param}>; rel="#{k}")
end

total_header = ApiPagination.config.total_header
per_page_header = ApiPagination.config.per_page_header
page_header = ApiPagination.config.page_header
include_total = ApiPagination.config.include_total

headers['Link'] = links.join(', ') unless links.empty?
headers[per_page_header] = options[:per_page].to_s
headers[page_header] = options[:page].to_s unless page_header.nil?
headers[total_header] = total_count(pagy || collection, options).to_s if include_total
headers[options[:per_page_header]] = options[:per_page].to_s
headers[options[:page_header]] = options[:page].to_s unless options[:page_header].nil?
headers[options[:total_header]] = total_count(pagy || collection, options).to_s if options[:include_total]

return collection
end

def total_count(collection, options)
total_count = if ApiPagination.config.paginator == :kaminari
total_count = if options[:paginator] == :kaminari
paginate_array_options = options[:paginate_array_options]
paginate_array_options[:total_count] if paginate_array_options
end
total_count || ApiPagination.total_from(collection)
total_count || ApiPagination.total_from(collection, options)
end

def base_url
Expand Down
23 changes: 23 additions & 0 deletions spec/grape_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,28 @@
expect(links).to include('<http://example.org/numbers?count=100&page=2&parity%5B%5D=odd&parity%5B%5D=even>; rel="next"')
end
end

context 'request option to not include the total' do
it 'should not include a Total header' do
get '/numbers_with_inline_options', count: 10

expect(last_response.header['Total']).to be_nil
end

it 'should not include a link with rel "last"' do
get '/numbers_with_inline_options', count: 100

expect(link).to_not include('rel="last"')
end
end

context 'request option to change page_header' do
it 'should give a X-Page header' do
get '/numbers_with_inline_options', count: 10

expect(last_response.headers.keys).to include('X-Page')
expect(last_response.headers['X-Page'].to_i).to eq(1)
end
end
end
end
25 changes: 24 additions & 1 deletion spec/rails_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,29 @@
expect(response.header['Per-Page']).to eq('2')
end
end

context 'request option to not include the total' do
it 'should not include a Total header' do
get :index_with_inline_options, params: {count: 10}

expect(response.header['Total']).to be_nil
end

it 'should not include a link with rel "last"' do
get :index_with_inline_options, params: { count: 100 }

expect(link).to_not include('rel="last"')
end
end

context 'request option to change page_header' do
it 'should give a X-Page header' do
get :index_with_inline_options, params: {count: 10}

expect(response.headers.keys).to include('X-Page')
expect(response.headers['X-Page'].to_i).to eq(1)
end
end
end

if ApiPagination.config.paginator.to_sym == :kaminari
Expand Down Expand Up @@ -309,4 +332,4 @@ class Fixnum
end
end
end
end
end
9 changes: 9 additions & 0 deletions spec/support/numbers_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,13 @@ class NumbersAPI < Grape::API
get :numbers_with_enforced_max_per_page do
paginate (1..params[:count]).to_a
end

desc 'Return some paginated set of numbers with inline options'
paginate :per_page => 10
params do
requires :count, :type => Integer
end
get :numbers_with_inline_options do
paginate (1..params[:count]).to_a, include_total: false, page_header: 'X-Page'
end
end
11 changes: 11 additions & 0 deletions spec/support/numbers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def teardown(*methods)
get :index_with_custom_render
get :index_with_no_per_page
get :index_with_paginate_array_options
get :index_with_inline_options
end
end
end
Expand Down Expand Up @@ -98,6 +99,16 @@ def index_with_paginate_array_options

render json: NumbersSerializer.new(numbers)
end

def index_with_inline_options
total = params.fetch(:count).to_i
paginate(
:json => (1..total).to_a,
:per_page => 10,
:include_total => false,
:page_header => 'X-Page'
)
end
end

ApiPagination::Railtie.initializers.each(&:run)

0 comments on commit 5eee138

Please sign in to comment.