Skip to content

Commit a00f3c0

Browse files
authored
Merge branch 'master' into fix-detect-model
2 parents 5c6309f + 45c10df commit a00f3c0

15 files changed

+173
-103
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ rvm:
77
- 2.5.0
88
script: bundle exec rspec
99
env:
10+
- PAGINATOR=pagy
1011
- PAGINATOR=kaminari
1112
- PAGINATOR=will_paginate

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ gemspec
55

66
gem 'kaminari', require: false
77
gem 'will_paginate', require: false
8+
gem 'pagy', require: false
89

910
gem 'sqlite3', require: false
1011
gem 'sequel', require: false
12+
gem 'pry-suite'

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# api-pagination
22

3-
[![Build Status][travis-badge]][travis] [![Coverage][coveralls-badge]][coveralls] [![Climate][code-climate-badge]][code-climate] [![Dependencies][gemnasium-badge]][gemnasium] [![gittip][gittip-badge]][gittip]
4-
53
Paginate in your headers, not in your response body.
64
This follows the proposed [RFC-5988](http://tools.ietf.org/html/rfc5988) standard for Web linking.
75

@@ -18,6 +16,7 @@ gem 'rails-api'
1816
gem 'grape', '>= 0.10.0'
1917

2018
# Then choose your preferred paginator from the following:
19+
gem 'pagy'
2120
gem 'kaminari'
2221
gem 'will_paginate'
2322

@@ -27,11 +26,11 @@ gem 'api-pagination'
2726

2827
## Configuration (optional)
2928

30-
By default, api-pagination will detect whether you're using Kaminari or WillPaginate, and name headers appropriately. If you want to change any of the configurable settings, you may do so:
29+
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:
3130

3231
```ruby
3332
ApiPagination.configure do |config|
34-
# If you have both gems included, you can choose a paginator.
33+
# If you have more than one gem included, you can choose a paginator.
3534
config.paginator = :kaminari # or :will_paginate
3635

3736
# By default, this is set to 'Total'
@@ -59,6 +58,16 @@ ApiPagination.configure do |config|
5958
end
6059
```
6160

61+
### Pagy-specific configuration
62+
63+
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:
64+
65+
```ruby
66+
Pagy::VARS[:max_per_page] = 100
67+
```
68+
69+
If left unconfigured, clients can request as many items per page as they wish, so it's highly recommended that you configure this.
70+
6271
## Rails
6372

6473
In your controller, provide a pageable collection to the `paginate` method. In its most convenient form, `paginate` simply mimics `render`:
@@ -103,7 +112,7 @@ class MoviesController < ApplicationController
103112
end
104113
```
105114

106-
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.
115+
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`.
107116

108117
**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:
109118

api-pagination.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
1818

1919
s.add_development_dependency 'rspec', '~> 3.0'
2020
s.add_development_dependency 'grape', '>= 0.10.0'
21-
s.add_development_dependency 'railties', '>= 3.0.0'
22-
s.add_development_dependency 'actionpack', '>= 3.0.0'
21+
s.add_development_dependency 'railties', '>= 5.0.0'
22+
s.add_development_dependency 'actionpack', '>= 5.0.0'
2323
s.add_development_dependency 'sequel', '>= 4.9.0'
2424
s.add_development_dependency 'activerecord-nulldb-adapter', '>= 0.3.8'
2525
end

lib/api-pagination.rb

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ def paginate(collection, options = {})
99
options[:per_page] = options[:per_page].to_i
1010

1111
case ApiPagination.config.paginator
12+
when :pagy
13+
paginate_with_pagy(collection, options)
1214
when :kaminari
1315
paginate_with_kaminari(collection, options, options[:paginate_array_options] || {})
1416
when :will_paginate
@@ -18,7 +20,9 @@ def paginate(collection, options = {})
1820
end
1921
end
2022

21-
def pages_from(collection)
23+
def pages_from(collection, options = {})
24+
return pagy_pages_from(collection) if collection.is_a?(Pagy)
25+
2226
{}.tap do |pages|
2327
unless collection.first_page?
2428
pages[:first] = 1
@@ -34,13 +38,49 @@ def pages_from(collection)
3438

3539
def total_from(collection)
3640
case ApiPagination.config.paginator
41+
when :pagy then collection.count.to_s
3742
when :kaminari then collection.total_count.to_s
3843
when :will_paginate then collection.total_entries.to_s
3944
end
4045
end
4146

4247
private
4348

49+
def paginate_with_pagy(collection, options)
50+
if Pagy::VARS[:max_per_page] && options[:per_page] > Pagy::VARS[:max_per_page]
51+
options[:per_page] = Pagy::VARS[:max_per_page]
52+
elsif options[:per_page] <= 0
53+
options[:per_page] = Pagy::VARS[:items]
54+
end
55+
56+
pagy = pagy_from(collection, options)
57+
collection = if collection.respond_to?(:offset) && collection.respond_to?(:limit)
58+
collection.offset(pagy.offset).limit(pagy.items)
59+
else
60+
collection[pagy.offset, pagy.items]
61+
end
62+
63+
return [collection, pagy]
64+
end
65+
66+
def pagy_from(collection, options)
67+
Pagy.new(count: collection.count, items: options[:per_page], page: options[:page])
68+
end
69+
70+
def pagy_pages_from(pagy)
71+
{}.tap do |pages|
72+
unless pagy.page == 1
73+
pages[:first] = 1
74+
pages[:prev] = pagy.prev
75+
end
76+
77+
unless pagy.page == pagy.pages
78+
pages[:last] = pagy.pages
79+
pages[:next] = pagy.next
80+
end
81+
end
82+
end
83+
4484
def paginate_with_kaminari(collection, options, paginate_array_options = {})
4585
if Kaminari.config.max_per_page && options[:per_page] > Kaminari.config.max_per_page
4686
options[:per_page] = Kaminari.config.max_per_page
@@ -49,21 +89,23 @@ def paginate_with_kaminari(collection, options, paginate_array_options = {})
4989
end
5090

5191
collection = Kaminari.paginate_array(collection, paginate_array_options) if collection.is_a?(Array)
52-
collection.page(options[:page]).per(options[:per_page])
92+
[collection.page(options[:page]).per(options[:per_page]), nil]
5393
end
5494

5595
def paginate_with_will_paginate(collection, options)
5696
if options[:per_page] <= 0
5797
options[:per_page] = default_per_page_for_will_paginate(collection)
5898
end
5999

60-
if defined?(Sequel::Dataset) && collection.kind_of?(Sequel::Dataset)
100+
collection = if defined?(Sequel::Dataset) && collection.kind_of?(Sequel::Dataset)
61101
collection.paginate(options[:page], options[:per_page])
62102
else
63103
supported_options = [:page, :per_page, :total_entries]
64104
options = options.dup.keep_if { |k,v| supported_options.include?(k.to_sym) }
65105
collection.paginate(options)
66106
end
107+
108+
[collection, nil]
67109
end
68110

69111
def get_default_per_page_for_kaminari(collection)

lib/api-pagination/configuration.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def paginator
5555

5656
def paginator=(paginator)
5757
case paginator.to_sym
58+
when :pagy
59+
use_pagy
5860
when :kaminari
5961
use_kaminari
6062
when :will_paginate
@@ -67,11 +69,12 @@ def paginator=(paginator)
6769
private
6870

6971
def set_paginator
70-
if defined?(Kaminari) && defined?(WillPaginate::CollectionMethods)
72+
conditions = [defined?(Pagy), defined?(Kaminari), defined?(WillPaginate::CollectionMethods)]
73+
if conditions.compact.size > 1
7174
Kernel.warn <<-WARNING
72-
Warning: api-pagination relies on either Kaminari or WillPaginate, but both are
73-
currently active. If possible, you should remove one or the other. If you can't,
74-
you _must_ configure api-pagination on your own. For example:
75+
Warning: api-pagination relies on Pagy, Kaminari, or WillPaginate, but more than
76+
one are currently active. If possible, you should remove one or the other. If
77+
you can't, you _must_ configure api-pagination on your own. For example:
7578
7679
ApiPagination.configure do |config|
7780
config.paginator = :kaminari
@@ -86,13 +89,19 @@ def set_paginator
8689
end
8790
8891
WARNING
92+
elsif defined?(Pagy)
93+
use_pagy
8994
elsif defined?(Kaminari)
90-
return use_kaminari
95+
use_kaminari
9196
elsif defined?(WillPaginate::CollectionMethods)
92-
return use_will_paginate
97+
use_will_paginate
9398
end
9499
end
95100

101+
def use_pagy
102+
@paginator = :pagy
103+
end
104+
96105
def use_kaminari
97106
require 'kaminari/models/array_extension'
98107
@paginator = :kaminari

lib/api-pagination/hooks.rb

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,36 @@
1-
begin; require 'rails'; rescue LoadError; end
2-
begin; require 'rails-api'; rescue LoadError; end
3-
4-
if defined?(Rails)
5-
module ApiPagination
6-
module Hooks
7-
def self.rails_parent_controller
8-
if Gem::Version.new(Rails.version) >= Gem::Version.new('5') || defined?(ActionController::API)
9-
ActionController::API
10-
else
11-
ActionController::Base
12-
end
13-
end
14-
end
15-
end
16-
17-
require 'rails/pagination'
18-
19-
ActiveSupport.on_load(:action_controller) do
20-
ApiPagination::Hooks.rails_parent_controller.send(:include, Rails::Pagination)
21-
end
22-
end
23-
241
begin; require 'grape'; rescue LoadError; end
252
if defined?(Grape::API)
263
require 'grape/pagination'
274
Grape::API.send(:include, Grape::Pagination)
285
end
296

7+
begin; require 'pagy'; rescue LoadError; end
308
begin; require 'kaminari'; rescue LoadError; end
319
begin; require 'will_paginate'; rescue LoadError; end
3210

33-
unless defined?(Kaminari) || defined?(WillPaginate::CollectionMethods)
11+
unless defined?(Pagy) || defined?(Kaminari) || defined?(WillPaginate::CollectionMethods)
3412
Kernel.warn <<-WARNING.gsub(/^\s{4}/, '')
35-
Warning: api-pagination relies on either Kaminari or WillPaginate. Please
36-
install either dependency by adding one of the following to your Gemfile:
13+
Warning: api-pagination relies on either Pagy, Kaminari, or WillPaginate.
14+
Please install a paginator by adding one of the following to your Gemfile:
3715
16+
gem 'pagy'
3817
gem 'kaminari'
3918
gem 'will_paginate'
4019
WARNING
4120
end
4221

22+
if defined?(Rails)
23+
module ApiPagination
24+
module Hooks
25+
def self.rails_parent_controller
26+
if Rails::VERSION::MAJOR >= 5 || defined?(ActionController::API)
27+
ActionController::API
28+
else
29+
ActionController::Base
30+
end
31+
end
32+
end
33+
end
34+
35+
require 'api-pagination/railtie'
36+
end

lib/api-pagination/railtie.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require 'rails/railtie'
2+
3+
module ApiPagination
4+
class Railtie < ::Rails::Railtie
5+
initializer :api_pagination do
6+
ActiveSupport.on_load(:action_controller) do
7+
require 'rails/pagination'
8+
9+
klass = if Rails::VERSION::MAJOR >= 5 || defined?(ActionController::API)
10+
ActionController::API
11+
else
12+
ActionController::Base
13+
end
14+
15+
klass.send(:include, Rails::Pagination)
16+
end
17+
end
18+
end
19+
end

lib/api-pagination/version.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module ApiPagination
22
class Version
33
MAJOR = 4
4-
MINOR = 7
5-
PATCH = 1
4+
MINOR = 8
5+
PATCH = 0
66

77
def self.to_s
88
[MAJOR, MINOR, PATCH].join('.')

lib/grape/pagination.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ def paginate(collection)
99
:page => ApiPagination.config.page_param(params),
1010
:per_page => [per_page, route_setting(:max_per_page)].compact.min
1111
}
12-
collection = ApiPagination.paginate(collection, options)
12+
collection, pagy = ApiPagination.paginate(collection, options)
1313

1414
links = (header['Link'] || "").split(',').map(&:strip)
1515
url = request.url.sub(/\?.*$/, '')
16-
pages = ApiPagination.pages_from(collection)
16+
pages = ApiPagination.pages_from(pagy || collection, options)
1717

1818
pages.each do |k, v|
1919
old_params = Rack::Utils.parse_nested_query(request.query_string)
@@ -27,7 +27,7 @@ def paginate(collection)
2727
include_total = ApiPagination.config.include_total
2828

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

lib/rails/pagination.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ def _paginate_collection(collection, options={})
2727
options[:page] = ApiPagination.config.page_param(params)
2828
options[:per_page] ||= ApiPagination.config.per_page_param(params)
2929

30-
collection = ApiPagination.paginate(collection, options)
30+
collection, pagy = ApiPagination.paginate(collection, options)
3131

3232
links = (headers['Link'] || '').split(',').map(&:strip)
3333
url = base_url + request.path_info
34-
pages = ApiPagination.pages_from(collection)
34+
pages = ApiPagination.pages_from(pagy || collection, options)
3535

3636
pages.each do |k, v|
3737
new_params = request.query_parameters.merge(:page => v)
@@ -46,7 +46,7 @@ def _paginate_collection(collection, options={})
4646
headers['Link'] = links.join(', ') unless links.empty?
4747
headers[per_page_header] = options[:per_page].to_s
4848
headers[page_header] = options[:page].to_s unless page_header.nil?
49-
headers[total_header] = total_count(collection, options).to_s if include_total
49+
headers[total_header] = total_count(pagy || collection, options).to_s if include_total
5050

5151
return collection
5252
end
@@ -62,6 +62,5 @@ def total_count(collection, options)
6262
def base_url
6363
ApiPagination.config.base_url || request.base_url
6464
end
65-
6665
end
6766
end

0 commit comments

Comments
 (0)