api-pagination 1.1.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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