api-pagination 1.1.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/api-pagination.rb +27 -25
- data/lib/api-pagination/hooks.rb +47 -0
- data/lib/api-pagination/version.rb +2 -2
- data/lib/grape/pagination.rb +37 -0
- data/lib/rails/pagination.rb +24 -0
- data/spec/grape_spec.rb +44 -0
- data/spec/rails_spec.rb +50 -0
- data/spec/spec_helper.rb +32 -37
- data/spec/support/numbers_api.rb +23 -0
- data/spec/support/numbers_controller.rb +63 -0
- data/spec/support/shared_examples/existing_headers.rb +10 -0
- data/spec/support/shared_examples/first_page.rb +17 -0
- data/spec/support/shared_examples/last_page.rb +17 -0
- data/spec/support/shared_examples/middle_page.rb +8 -0
- metadata +45 -17
- data/spec/controllers/numbers_controller_spec.rb +0 -109
- data/spec/dummy/log/development.log +0 -17807
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39ada68101786c8ae919218460cd300a41c03dab
|
4
|
+
data.tar.gz: e738863fb5ca287442afa734d7b87a01c0173e86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 332904cbbbcb0bbfee70edab279f0aecb400d8c3d4e8b76db5ecbec51ff532132467060b5b8a238436c846ab8b4f48b5927eb8575bf1878cc31e74227a8bcb0f
|
7
|
+
data.tar.gz: 3d9bad3a6aff7918d9f104abb8fa351566c7d8c745afe0178d0bb1afed61a611bb95a01c7bb7abef07687ec82b747421d2e663ca1aa7e615e3b3aab9ce8793c7
|
data/lib/api-pagination.rb
CHANGED
@@ -1,37 +1,39 @@
|
|
1
|
+
require 'api-pagination/hooks'
|
1
2
|
require 'api-pagination/version'
|
2
3
|
|
3
4
|
module ApiPagination
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
url = request.original_url.sub(/\?.*$/, '')
|
8
|
-
pages = {}
|
5
|
+
class << self
|
6
|
+
attr_writer :kaminari
|
7
|
+
attr_writer :will_paginate
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
pages[:prev] = scope.current_page - 1
|
13
|
-
end
|
9
|
+
def kaminari?() !!@kaminari end
|
10
|
+
def will_paginate?() !!@will_paginate end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
12
|
+
def paginate(collection, options = {}, &block)
|
13
|
+
options[:page] ||= 1
|
14
|
+
options[:per_page] ||= 10
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
if ApiPagination.kaminari?
|
17
|
+
collection.page(options[:page]).per(options[:per_page]).tap(&block)
|
18
|
+
elsif ApiPagination.will_paginate?
|
19
|
+
collection.paginate(:page => options[:page], :per_page => options[:per_page]).tap(&block)
|
23
20
|
end
|
24
|
-
|
25
|
-
headers['Link'] = links.join(', ') unless links.empty?
|
26
21
|
end
|
27
|
-
end
|
28
22
|
|
29
|
-
|
30
|
-
|
23
|
+
def pages_from(collection)
|
24
|
+
{}.tap do |pages|
|
25
|
+
unless collection.first_page?
|
26
|
+
pages[:first] = 1
|
27
|
+
pages[:prev] = collection.current_page - 1
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
unless collection.last_page?
|
31
|
+
pages[:last] = collection.total_pages
|
32
|
+
pages[:next] = collection.current_page + 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
36
|
end
|
37
37
|
end
|
38
|
+
|
39
|
+
ApiPagination::Hooks.init
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ApiPagination
|
2
|
+
class Hooks
|
3
|
+
def self.init
|
4
|
+
begin; require 'rails'; rescue LoadError; end
|
5
|
+
if defined?(ActionController::Base)
|
6
|
+
require 'rails/pagination'
|
7
|
+
ActionController::Base.send(:include, Rails::Pagination)
|
8
|
+
end
|
9
|
+
|
10
|
+
begin; require 'rails-api'; rescue LoadError; end
|
11
|
+
if defined?(ActionController::API)
|
12
|
+
require 'rails/pagination'
|
13
|
+
ActionController::API.send(:include, Rails::Pagination)
|
14
|
+
end
|
15
|
+
|
16
|
+
begin; require 'grape'; rescue LoadError; end
|
17
|
+
if defined?(Grape::API)
|
18
|
+
require 'grape/pagination'
|
19
|
+
Grape::API.send(:include, Grape::Pagination)
|
20
|
+
end
|
21
|
+
|
22
|
+
begin; require 'will_paginate'; rescue LoadError; end
|
23
|
+
if defined?(WillPaginate::CollectionMethods)
|
24
|
+
WillPaginate::CollectionMethods.module_eval do
|
25
|
+
def first_page?() !previous_page end
|
26
|
+
def last_page?() !next_page end
|
27
|
+
end
|
28
|
+
|
29
|
+
ApiPagination.will_paginate = true
|
30
|
+
end
|
31
|
+
|
32
|
+
begin; require 'kaminari'; rescue LoadError; end
|
33
|
+
if defined?(Kaminari)
|
34
|
+
ApiPagination.kaminari = true
|
35
|
+
end
|
36
|
+
|
37
|
+
STDERR.puts <<-EOC unless defined?(Kaminari) || defined?(WillPaginate)
|
38
|
+
Warning: api-pagination relies on either Kaminari or WillPaginate. Please
|
39
|
+
install either dependency by adding one of the following to your Gemfile:
|
40
|
+
|
41
|
+
gem 'kaminari'
|
42
|
+
gem 'will_paginate'
|
43
|
+
|
44
|
+
EOC
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Grape
|
2
|
+
module Pagination
|
3
|
+
def self.included(base)
|
4
|
+
Grape::Endpoint.class_eval do
|
5
|
+
def paginate(collection)
|
6
|
+
block = Proc.new do |collection|
|
7
|
+
links = (header['Link'] || "").split(',').map(&:strip)
|
8
|
+
url = request.url.sub(/\?.*$/, '')
|
9
|
+
pages = ApiPagination.pages_from(collection)
|
10
|
+
|
11
|
+
pages.each do |k, v|
|
12
|
+
old_params = Rack::Utils.parse_query(request.query_string)
|
13
|
+
new_params = old_params.merge('page' => v)
|
14
|
+
links << %(<#{url}?#{new_params.to_param}>; rel="#{k}")
|
15
|
+
end
|
16
|
+
|
17
|
+
header 'Link', links.join(', ') unless links.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
ApiPagination.paginate(collection, params, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
base.class_eval do
|
25
|
+
def self.paginate(options = {})
|
26
|
+
options.reverse_merge!(:per_page => 10)
|
27
|
+
params do
|
28
|
+
optional :page, :type => Integer, :default => 1,
|
29
|
+
:desc => 'Page of results to fetch.'
|
30
|
+
optional :per_page, :type => Integer, :default => options[:per_page],
|
31
|
+
:desc => 'Number of results to return per page.'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rails
|
2
|
+
module Pagination
|
3
|
+
protected
|
4
|
+
|
5
|
+
def paginate(collection)
|
6
|
+
collection = instance_variable_get(:"@#{collection}")
|
7
|
+
|
8
|
+
block = Proc.new do |collection|
|
9
|
+
links = (headers['Link'] || "").split(',').map(&:strip)
|
10
|
+
url = request.original_url.sub(/\?.*$/, '')
|
11
|
+
pages = ApiPagination.pages_from(collection)
|
12
|
+
|
13
|
+
pages.each do |k, v|
|
14
|
+
new_params = request.query_parameters.merge(:page => v)
|
15
|
+
links << %(<#{url}?#{new_params.to_param}>; rel="#{k}")
|
16
|
+
end
|
17
|
+
|
18
|
+
headers['Link'] = links.join(', ') unless links.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
ApiPagination.paginate(collection, params, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/grape_spec.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/shared_examples/existing_headers'
|
3
|
+
require 'support/shared_examples/first_page'
|
4
|
+
require 'support/shared_examples/middle_page'
|
5
|
+
require 'support/shared_examples/last_page'
|
6
|
+
|
7
|
+
describe NumbersAPI do
|
8
|
+
describe 'GET #index' do
|
9
|
+
let(:links) { last_response.headers['Link'].split(', ') }
|
10
|
+
|
11
|
+
context 'without enough items to give more than one page' do
|
12
|
+
it 'should not paginate' do
|
13
|
+
get :numbers, :count => 20
|
14
|
+
expect(last_response.headers.keys).not_to include('Link')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'with existing Link headers' do
|
19
|
+
before { get :numbers, :count => 30, :with_headers => true }
|
20
|
+
|
21
|
+
it_behaves_like 'an endpoint with existing Link headers'
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with enough items to paginate' do
|
25
|
+
context 'when on the first page' do
|
26
|
+
before { get :numbers, :count => 100 }
|
27
|
+
|
28
|
+
it_behaves_like 'an endpoint with a first page'
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when on the last page' do
|
32
|
+
before { get :numbers, :count => 100, :page => 4 }
|
33
|
+
|
34
|
+
it_behaves_like 'an endpoint with a last page'
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when somewhere comfortably in the middle' do
|
38
|
+
before { get :numbers, :count => 100, :page => 2 }
|
39
|
+
|
40
|
+
it_behaves_like 'an endpoint with a middle page'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/rails_spec.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/shared_examples/existing_headers'
|
3
|
+
require 'support/shared_examples/first_page'
|
4
|
+
require 'support/shared_examples/middle_page'
|
5
|
+
require 'support/shared_examples/last_page'
|
6
|
+
|
7
|
+
describe NumbersController, :type => :controller do
|
8
|
+
before { request.host = 'example.org' }
|
9
|
+
describe 'GET #index' do
|
10
|
+
let(:links) { response.headers['Link'].split(', ') }
|
11
|
+
|
12
|
+
context 'without enough items to give more than one page' do
|
13
|
+
it 'should not paginate' do
|
14
|
+
get :index, :count => 20
|
15
|
+
expect(response.headers.keys).not_to include('Link')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with existing Link headers' do
|
20
|
+
before { get :index, :count => 30, :with_headers => true }
|
21
|
+
|
22
|
+
it_behaves_like 'an endpoint with existing Link headers'
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with enough items to paginate' do
|
26
|
+
context 'when on the first page' do
|
27
|
+
before { get :index, :count => 100 }
|
28
|
+
|
29
|
+
it_behaves_like 'an endpoint with a first page'
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when on the last page' do
|
33
|
+
before { get :index, :count => 100, :page => 4 }
|
34
|
+
|
35
|
+
it_behaves_like 'an endpoint with a last page'
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when somewhere comfortably in the middle' do
|
39
|
+
before { get :index, :count => 100, :page => 2 }
|
40
|
+
|
41
|
+
it_behaves_like 'an endpoint with a middle page'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -1,49 +1,42 @@
|
|
1
|
-
require '
|
1
|
+
require 'support/numbers_controller'
|
2
|
+
require 'support/numbers_api'
|
2
3
|
require 'api-pagination'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@application ||= begin
|
9
|
-
routes = ActionDispatch::Routing::RouteSet.new
|
10
|
-
OpenStruct.new(routes: routes, env_config: {})
|
11
|
-
end
|
4
|
+
|
5
|
+
# Quacks like Kaminari and will_paginate
|
6
|
+
PaginatedSet = Struct.new(:current_page, :per_page, :total_count) do
|
7
|
+
def total_pages
|
8
|
+
total_count.zero? ? 1 : (total_count.to_f / per_page).ceil
|
12
9
|
end
|
13
|
-
end
|
14
10
|
|
15
|
-
|
16
|
-
def
|
17
|
-
base.extend ClassMethods
|
18
|
-
base.send(:include, ActionController::TestCase::Behavior)
|
11
|
+
def first_page?() current_page == 1 end
|
12
|
+
def last_page?() current_page == total_pages end
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
14
|
+
def page(page)
|
15
|
+
current_page = page
|
16
|
+
self
|
24
17
|
end
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def teardown(*methods)
|
38
|
-
methods.each { |method| after { send method } }
|
39
|
-
end
|
19
|
+
def per(per)
|
20
|
+
per_page = per
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def paginate(options = {})
|
25
|
+
page(options[:page]).per(options[:per_page])
|
40
26
|
end
|
41
27
|
end
|
42
28
|
|
29
|
+
ApiPagination.kaminari = ENV['PAGINATOR'] == 'kaminari'
|
30
|
+
ApiPagination.will_paginate = ENV['PAGINATOR'] == 'will_paginate'
|
31
|
+
|
43
32
|
RSpec.configure do |config|
|
44
|
-
config.
|
45
|
-
config.
|
46
|
-
|
33
|
+
config.include Rack::Test::Methods
|
34
|
+
config.include ControllerExampleGroup, :type => :controller
|
35
|
+
|
36
|
+
# Disable the 'should' syntax.
|
37
|
+
config.expect_with :rspec do |c|
|
38
|
+
c.syntax = :expect
|
39
|
+
end
|
47
40
|
|
48
41
|
# Run specs in random order to surface order dependencies. If you find an
|
49
42
|
# order dependency and want to debug it, you can fix the order by providing
|
@@ -51,5 +44,7 @@ RSpec.configure do |config|
|
|
51
44
|
# --seed 1234
|
52
45
|
config.order = 'random'
|
53
46
|
|
54
|
-
|
47
|
+
def app
|
48
|
+
NumbersAPI
|
49
|
+
end
|
55
50
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'grape'
|
2
|
+
require 'api-pagination'
|
3
|
+
|
4
|
+
class NumbersAPI < Grape::API
|
5
|
+
format :json
|
6
|
+
|
7
|
+
desc 'Return some paginated set of numbers'
|
8
|
+
paginate :per_page => 25
|
9
|
+
params do
|
10
|
+
requires :count, :type => Integer
|
11
|
+
optional :with_headers, :default => false, :type => Boolean
|
12
|
+
end
|
13
|
+
get :numbers do
|
14
|
+
if params[:with_headers]
|
15
|
+
url = request.url.sub(/\?.*/, '')
|
16
|
+
query = Rack::Utils.parse_query(request.query_string)
|
17
|
+
query.delete('with_headers')
|
18
|
+
header 'Link', %(<#{url}?#{query.to_query}>; rel="without")
|
19
|
+
end
|
20
|
+
|
21
|
+
paginate PaginatedSet.new(params[:page], params[:per_page], params[:count])
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Rails
|
5
|
+
def self.application
|
6
|
+
@application ||= begin
|
7
|
+
routes = ActionDispatch::Routing::RouteSet.new
|
8
|
+
OpenStruct.new(:routes => routes, :env_config => {})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ControllerExampleGroup
|
14
|
+
def self.included(base)
|
15
|
+
base.extend ClassMethods
|
16
|
+
base.send(:include, ActionController::TestCase::Behavior)
|
17
|
+
|
18
|
+
base.prepend_before do
|
19
|
+
@routes = Rails.application.routes
|
20
|
+
@controller = described_class.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def setup(*methods)
|
26
|
+
methods.each do |method|
|
27
|
+
if method.to_s =~ /^setup_(fixtures|controller_request_and_response)$/
|
28
|
+
prepend_before { send method }
|
29
|
+
else
|
30
|
+
before { send method }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def teardown(*methods)
|
36
|
+
methods.each { |method| after { send method } }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Rails.application.routes.draw do
|
42
|
+
resources :numbers, :only => [:index]
|
43
|
+
end
|
44
|
+
|
45
|
+
class NumbersController < ActionController::Base
|
46
|
+
include Rails.application.routes.url_helpers
|
47
|
+
|
48
|
+
after_filter :only => [:index] { paginate(:numbers) }
|
49
|
+
|
50
|
+
def index
|
51
|
+
page = params.fetch(:page, 1).to_i
|
52
|
+
total = params.fetch(:count).to_i
|
53
|
+
|
54
|
+
if params[:with_headers]
|
55
|
+
query = request.query_parameters.dup
|
56
|
+
query.delete(:with_headers)
|
57
|
+
headers['Link'] = %(<#{numbers_url}?#{query.to_param}>; rel="without")
|
58
|
+
end
|
59
|
+
|
60
|
+
@numbers = PaginatedSet.new(page, 25, total)
|
61
|
+
render :json => @numbers
|
62
|
+
end
|
63
|
+
end
|