Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuration overrides from request options #111

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)