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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 09a9dd73ccebd7280dbb49978e11f0f667f178b4
4
- data.tar.gz: dd0ee8715806ce59a197e76ab9e3fdec7a189634
3
+ metadata.gz: 39ada68101786c8ae919218460cd300a41c03dab
4
+ data.tar.gz: e738863fb5ca287442afa734d7b87a01c0173e86
5
5
  SHA512:
6
- metadata.gz: df67ca42a6de5f48e9c38b72981ecb6ea78a4cadeeff295811c229ded4e9180633c77133d0d28aebf8bbeb5834afdbe28d42f2c273e624a854ba63f7ecf48bfd
7
- data.tar.gz: f836023f6066bb9b3269924e0af441d77881ee1c7448ae5063c293ada7a6d47b843ab5339a61c4b8a70279fc9f59db42195e3b727cc54d7038e6672c7cbdecef
6
+ metadata.gz: 332904cbbbcb0bbfee70edab279f0aecb400d8c3d4e8b76db5ecbec51ff532132467060b5b8a238436c846ab8b4f48b5927eb8575bf1878cc31e74227a8bcb0f
7
+ data.tar.gz: 3d9bad3a6aff7918d9f104abb8fa351566c7d8c745afe0178d0bb1afed61a611bb95a01c7bb7abef07687ec82b747421d2e663ca1aa7e615e3b3aab9ce8793c7
@@ -1,37 +1,39 @@
1
+ require 'api-pagination/hooks'
1
2
  require 'api-pagination/version'
2
3
 
3
4
  module ApiPagination
4
- protected
5
- def paginate(scope)
6
- scope = instance_variable_get(:"@#{scope}")
7
- url = request.original_url.sub(/\?.*$/, '')
8
- pages = {}
5
+ class << self
6
+ attr_writer :kaminari
7
+ attr_writer :will_paginate
9
8
 
10
- unless scope.first_page?
11
- pages[:first] = 1
12
- pages[:prev] = scope.current_page - 1
13
- end
9
+ def kaminari?() !!@kaminari end
10
+ def will_paginate?() !!@will_paginate end
14
11
 
15
- unless scope.last_page?
16
- pages[:last] = scope.total_pages
17
- pages[:next] = scope.current_page + 1
18
- end
12
+ def paginate(collection, options = {}, &block)
13
+ options[:page] ||= 1
14
+ options[:per_page] ||= 10
19
15
 
20
- links = pages.map do |k, v|
21
- new_params = request.query_parameters.merge({ :page => v })
22
- %(<#{url}?#{new_params.to_param}>; rel="#{k}")
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
- ActionController::Base.send(:include, ApiPagination) if defined?(ActionController::Base)
30
- ActionController::API.send(:include, ApiPagination) if defined?(ActionController::API)
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
- if defined?(WillPaginate::CollectionMethods)
33
- WillPaginate::CollectionMethods.module_eval do
34
- def first_page?() !previous_page end
35
- def last_page?() !next_page end
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
@@ -1,6 +1,6 @@
1
1
  module ApiPagination
2
- MAJOR = 1
3
- MINOR = 1
2
+ MAJOR = 2
3
+ MINOR = 0
4
4
  PATCH = 1
5
5
 
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
@@ -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
@@ -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
@@ -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
+
@@ -1,49 +1,42 @@
1
- require 'action_controller'
1
+ require 'support/numbers_controller'
2
+ require 'support/numbers_api'
2
3
  require 'api-pagination'
3
- require 'rspec/autorun'
4
- require 'ostruct'
5
-
6
- module Rails
7
- def self.application
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
- module ControllerExampleGroup
16
- def self.included(base)
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
- base.prepend_before do
21
- @routes = Rails.application.routes
22
- @controller = described_class.new
23
- end
14
+ def page(page)
15
+ current_page = page
16
+ self
24
17
  end
25
18
 
26
- module ClassMethods
27
- def setup(*methods)
28
- methods.each do |method|
29
- if method.to_s =~ /^setup_(fixtures|controller_request_and_response)$/
30
- prepend_before { send method }
31
- else
32
- before { send method }
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.treat_symbols_as_metadata_keys_with_true_values = true
45
- config.run_all_when_everything_filtered = true
46
- config.filter_run :focus
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
- config.include ControllerExampleGroup, :type => :controller
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