pax_global_header00006660000000000000000000000064126415556630014527gustar00rootroot0000000000000052 comment=3db819fdfdd59c2ae64ca8977d76cff9cbd18bc6 api-pagination-4.2.0/000077500000000000000000000000001264155566300144325ustar00rootroot00000000000000api-pagination-4.2.0/.gitignore000066400000000000000000000002501264155566300164170ustar00rootroot00000000000000*.gem *.rbc .bundle .config .yardoc .ruby-version Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp api-pagination-4.2.0/.rspec000066400000000000000000000000321264155566300155420ustar00rootroot00000000000000--color --format progress api-pagination-4.2.0/.travis.yml000066400000000000000000000002441264155566300165430ustar00rootroot00000000000000language: ruby sudo: false rvm: - 1.9.3 - 2.0.0 - 2.1.0 - 2.2.0 - 2.3.0 script: bundle exec rspec env: - PAGINATOR=kaminari - PAGINATOR=will_paginate api-pagination-4.2.0/CONTRIBUTING.md000066400000000000000000000005371264155566300166700ustar00rootroot00000000000000## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes and tests (`git commit -am 'Add some feature'`) 4. Run the tests (`PAGINATOR=kaminari bundle exec rspec; PAGINATOR=will_paginate bundle exec rspec`) 5. Push to the branch (`git push origin my-new-feature`) 6. Create a new Pull Request api-pagination-4.2.0/Gemfile000066400000000000000000000003431264155566300157250ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in api_pagination.gemspec gemspec gem 'kaminari', require: false gem 'will_paginate', require: false gem 'sqlite3', require: false gem 'sequel', require: false api-pagination-4.2.0/LICENSE.txt000066400000000000000000000020531264155566300162550ustar00rootroot00000000000000Copyright © 2014 David Celis MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. api-pagination-4.2.0/README.md000066400000000000000000000144761264155566300157250ustar00rootroot00000000000000# api-pagination [![Build Status][travis-badge]][travis] [![Coverage][coveralls-badge]][coveralls] [![Climate][code-climate-badge]][code-climate] [![Dependencies][gemnasium-badge]][gemnasium] [![gittip][gittip-badge]][gittip] Paginate in your headers, not in your response body. This follows the proposed [RFC-5988](http://tools.ietf.org/html/rfc5988) standard for Web linking. ## Installation In your `Gemfile`: ```ruby # Requires Rails (Rails-API is also supported), or Grape # v0.10.0 or later. If you're on an earlier version of # Grape, use api-pagination 3.x gem 'rails', '>= 3.0.0' gem 'rails-api' gem 'grape', '>= 0.10.0' # Then choose your preferred paginator from the following: gem 'kaminari' gem 'will_paginate' # Finally... gem 'api-pagination' ``` ## Configuration (optional) 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: ```ruby ApiPagination.configure do |config| # If you have both gems included, you can choose a paginator. config.paginator = :kaminari # or :will_paginate # By default, this is set to 'Total' config.total_header = 'X-Total' # By default, this is set to 'Per-Page' config.per_page_header = 'X-Per-Page' # Optional : set this to add a header with the current page number. config.page_header = 'X-Page' end ``` ## Rails In your controller, provide a pageable collection to the `paginate` method. In its most convenient form, `paginate` simply mimics `render`: ```ruby class MoviesController < ApplicationController # GET /movies def index movies = Movie.all # Movie.scoped if using ActiveRecord 3.x paginate json: movies end # GET /movies/:id/cast def cast actors = Movie.find(params[:id]).actors # Override how many Actors get returned. If unspecified, # params[:per_page] (which defaults to 25) will be used. paginate json: actors, per_page: 10 end end ``` This will pull your collection from the `json` or `xml` option, paginate it for you using `params[:page]` and `params[:per_page]`, render Link headers, and call `ActionController::Base#render` with whatever you passed to `paginate`. This should work well with [ActiveModel::Serializers](https://github.com/rails-api/active_model-serializers). However, if you need more control over what is done with your paginated collection, you can pass the collection directly to `paginate` to receive a paginated collection and have your headers set. Then, you can pass that paginated collection to a serializer or do whatever you want with it: ```ruby class MoviesController < ApplicationController # GET /movies def index movies = paginate Movie.all render json: MoviesSerializer.new(movies) end # GET /movies/:id/cast def cast actors = paginate Movie.find(params[:id]).actors, per_page: 10 render json: ActorsSerializer.new(actors) end end ``` 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. ## Grape With Grape, `paginate` is used to declare that your endpoint takes a `:page` and `:per_page` param. You can also directly specify a `:max_per_page` that users aren't allowed to go over. Then, inside your API endpoint, it simply takes your collection: ```ruby class MoviesAPI < Grape::API format :json desc 'Return a paginated set of movies' paginate get do # This method must take an ActiveRecord::Relation # or some equivalent pageable set. paginate Movie.all end route_param :id do desc "Return one movie's cast, paginated" # Override how many Actors get returned. If unspecified, # params[:per_page] (which defaults to 25) will be used. # There is no default for `max_per_page`. paginate per_page: 10, max_per_page: 200 get :cast do paginate Movie.find(params[:id]).actors end end end ``` ## Headers Then `curl --include` to see your header-based pagination in action: ```bash $ curl --include 'https://localhost:3000/movies?page=5' HTTP/1.1 200 OK Link: ; rel="first", ; rel="last", ; rel="next", ; rel="prev" Total: 4321 Per-Page: 10 # ... ``` ## A Note on Kaminari and WillPaginate api-pagination requires either Kaminari or WillPaginate in order to function, but some users may find themselves in situations where their application includes both. For example, you may have included [ActiveAdmin][activeadmin] (which uses Kaminari for pagination) and WillPaginate to do your own pagination. While it's suggested that you remove one paginator gem or the other, if you're unable to do so, you _must_ configure api-pagination explicitly: ```ruby ApiPagination.configure do |config| config.paginator = :will_paginate end ``` If you don't do this, an annoying warning will print once your app starts seeing traffic. You should also configure Kaminari to use a different name for its `per_page` method (see https://github.com/activeadmin/activeadmin/wiki/How-to-work-with-will_paginate): ```ruby Kaminari.configure do |config| config.page_method_name = :per_page_kaminari end ``` [activeadmin]: https://github.com/activeadmin/activeadmin [kaminari]: https://github.com/amatsuda/kaminari [will_paginate]: https://github.com/mislav/will_paginate [travis]: https://travis-ci.org/davidcelis/api-pagination [travis-badge]: http://img.shields.io/travis/davidcelis/api-pagination/master.svg [coveralls]: https://coveralls.io/r/davidcelis/api-pagination [coveralls-badge]: http://img.shields.io/coveralls/davidcelis/api-pagination/master.svg [code-climate]: https://codeclimate.com/github/davidcelis/api-pagination [code-climate-badge]: http://img.shields.io/codeclimate/github/davidcelis/api-pagination.svg [gemnasium]: http://gemnasium.com/davidcelis/api-pagination [gemnasium-badge]: http://img.shields.io/gemnasium/davidcelis/api-pagination.svg [gittip]: https://gittip.com/davidcelis [gittip-badge]: http://img.shields.io/gittip/davidcelis.svg api-pagination-4.2.0/api-pagination.gemspec000066400000000000000000000016261264155566300207040ustar00rootroot00000000000000# encoding: utf-8 $:.unshift(File.expand_path('../lib', __FILE__)) require 'api-pagination/version' Gem::Specification.new do |s| s.name = 'api-pagination' s.version = ApiPagination::VERSION s.authors = ['David Celis'] s.email = ['me@davidcel.is'] s.description = 'Link header pagination for Rails and Grape APIs' s.summary = "Link header pagination for Rails and Grape APIs. Don't use the request body." s.homepage = 'https://github.com/davidcelis/api-pagination' s.license = 'MIT' s.files = Dir['lib/**/*'] s.test_files = Dir['spec/**/*'] s.require_paths = ['lib'] s.add_development_dependency 'rspec', '~> 3.0' s.add_development_dependency 'grape', '>= 0.10.0' s.add_development_dependency 'railties', '>= 3.0.0' s.add_development_dependency 'actionpack', '>= 3.0.0' s.add_development_dependency 'sequel', '>= 4.9.0' end api-pagination-4.2.0/lib/000077500000000000000000000000001264155566300152005ustar00rootroot00000000000000api-pagination-4.2.0/lib/api-pagination.rb000066400000000000000000000042141264155566300204260ustar00rootroot00000000000000require 'api-pagination/configuration' require 'api-pagination/version' 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 case ApiPagination.config.paginator 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}" end end def pages_from(collection) {}.tap do |pages| unless collection.first_page? pages[:first] = 1 pages[:prev] = collection.current_page - 1 end unless collection.last_page? pages[:last] = collection.total_pages pages[:next] = collection.current_page + 1 end end end def total_from(collection) case ApiPagination.config.paginator when :kaminari then collection.total_count.to_s when :will_paginate then collection.total_entries.to_s end end private def paginate_with_kaminari(collection, options, paginate_array_options = {}) if Kaminari.config.max_per_page && options[:per_page] > Kaminari.config.max_per_page options[:per_page] = Kaminari.config.max_per_page elsif options[:per_page] <= 0 options[:per_page] = Kaminari.config.default_per_page end collection = Kaminari.paginate_array(collection, paginate_array_options) if collection.is_a?(Array) collection.page(options[:page]).per(options[:per_page]) end def paginate_with_will_paginate(collection, options) options[:per_page] = WillPaginate.per_page if options[:per_page] <= 0 if defined?(Sequel::Dataset) && collection.kind_of?(Sequel::Dataset) collection.paginate(options[:page], options[:per_page]) else collection.paginate(:page => options[:page], :per_page => options[:per_page]) end end end end require 'api-pagination/hooks' api-pagination-4.2.0/lib/api-pagination/000077500000000000000000000000001264155566300201005ustar00rootroot00000000000000api-pagination-4.2.0/lib/api-pagination/configuration.rb000066400000000000000000000041041264155566300232730ustar00rootroot00000000000000module ApiPagination class Configuration attr_accessor :total_header attr_accessor :per_page_header attr_accessor :page_header attr_accessor :include_total def configure(&block) yield self end def initialize @total_header = 'Total' @per_page_header = 'Per-Page' @page_header = nil @include_total = true end def paginator @paginator || set_paginator end def paginator=(paginator) case paginator.to_sym when :kaminari use_kaminari when :will_paginate use_will_paginate else raise StandardError, "Unknown paginator: #{paginator}" end end private def set_paginator if defined?(Kaminari) && defined?(WillPaginate::CollectionMethods) Kernel.warn <<-WARNING Warning: api-pagination relies on either Kaminari or WillPaginate, but both are currently active. If possible, you should remove one or the other. If you can't, you _must_ configure api-pagination on your own. For example: ApiPagination.configure do |config| config.paginator = :kaminari end You should also configure Kaminari to use a different `per_page` method name as using these gems together causes a conflict; some information can be found at https://github.com/activeadmin/activeadmin/wiki/How-to-work-with-will_paginate Kaminari.configure do |config| config.page_method_name = :per_page_kaminari end WARNING elsif defined?(Kaminari) return use_kaminari elsif defined?(WillPaginate::CollectionMethods) return use_will_paginate end end def use_kaminari require 'kaminari/models/array_extension' @paginator = :kaminari end def use_will_paginate WillPaginate::CollectionMethods.module_eval do def first_page?() !previous_page end def last_page?() !next_page end end @paginator = :will_paginate end end class << self def configure yield config end def config @config ||= Configuration.new end alias :configuration :config end end api-pagination-4.2.0/lib/api-pagination/hooks.rb000066400000000000000000000016551264155566300215570ustar00rootroot00000000000000begin; require 'rails'; rescue LoadError; end if defined?(ActionController::Base) require 'rails/pagination' ActionController::Base.send(:include, Rails::Pagination) end begin; require 'rails-api'; rescue LoadError; end if defined?(ActionController::API) require 'rails/pagination' ActionController::API.send(:include, Rails::Pagination) end begin; require 'grape'; rescue LoadError; end if defined?(Grape::API) require 'grape/pagination' Grape::API.send(:include, Grape::Pagination) end begin; require 'kaminari'; rescue LoadError; end begin; require 'will_paginate'; rescue LoadError; end unless defined?(Kaminari) || defined?(WillPaginate::CollectionMethods) Kernel.warn <<-WARNING.gsub(/^\s{4}/, '') Warning: api-pagination relies on either Kaminari or WillPaginate. Please install either dependency by adding one of the following to your Gemfile: gem 'kaminari' gem 'will_paginate' WARNING end api-pagination-4.2.0/lib/api-pagination/version.rb000066400000000000000000000002641264155566300221140ustar00rootroot00000000000000module ApiPagination class Version MAJOR = 4 MINOR = 2 PATCH = 0 def self.to_s [MAJOR, MINOR, PATCH].join('.') end end VERSION = Version.to_s end api-pagination-4.2.0/lib/grape/000077500000000000000000000000001264155566300162765ustar00rootroot00000000000000api-pagination-4.2.0/lib/grape/pagination.rb000066400000000000000000000036371264155566300207650ustar00rootroot00000000000000module Grape module Pagination def self.included(base) Grape::Endpoint.class_eval do def paginate(collection) per_page = params[:per_page] || route_setting(:per_page) options = { :page => params[:page], :per_page => [per_page, route_setting(:max_per_page)].compact.min } collection = ApiPagination.paginate(collection, options) links = (header['Link'] || "").split(',').map(&:strip) url = request.url.sub(/\?.*$/, '') pages = ApiPagination.pages_from(collection) pages.each do |k, v| old_params = Rack::Utils.parse_nested_query(request.query_string) new_params = old_params.merge('page' => v) 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(collection) if include_total header per_page_header, options[:per_page].to_s header page_header, options[:page].to_s unless page_header.nil? return collection end end base.class_eval do def self.paginate(options = {}) route_setting :per_page, options[:per_page] route_setting :max_per_page, options[:max_per_page] params do optional :page, :type => Integer, :default => 1, :desc => 'Page of results to fetch.' optional :per_page, :type => Integer, :desc => 'Number of results to return per page.' end end end end end end api-pagination-4.2.0/lib/rails/000077500000000000000000000000001264155566300163125ustar00rootroot00000000000000api-pagination-4.2.0/lib/rails/pagination.rb000066400000000000000000000033531264155566300207740ustar00rootroot00000000000000module Rails module Pagination protected def paginate(*options_or_collection) options = options_or_collection.extract_options! collection = options_or_collection.first return _paginate_collection(collection, options) if collection collection = options[:json] || options[:xml] collection = _paginate_collection(collection, options) options[:json] = collection if options[:json] options[:xml] = collection if options[:xml] render options end def paginate_with(collection) respond_with _paginate_collection(collection) end private def _paginate_collection(collection, options={}) options = { :page => params[:page], :per_page => (options.delete(:per_page) || params[:per_page]) } collection = ApiPagination.paginate(collection, options) links = (headers['Link'] || "").split(',').map(&:strip) url = request.original_url.sub(/\?.*$/, '') pages = ApiPagination.pages_from(collection) pages.each do |k, v| new_params = request.query_parameters.merge(:page => v) 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[total_header] = ApiPagination.total_from(collection) if include_total headers[per_page_header] = options[:per_page].to_s headers[page_header] = options[:page].to_s unless page_header.nil? return collection end end end api-pagination-4.2.0/spec/000077500000000000000000000000001264155566300153645ustar00rootroot00000000000000api-pagination-4.2.0/spec/api-pagination_spec.rb000066400000000000000000000013351264155566300216250ustar00rootroot00000000000000require 'spec_helper' describe ApiPagination do let(:collection) { (1..100).to_a } let(:paginate_array_options) { { total_count: 1000 } } context 'Using kaminari' do before do ApiPagination.config.paginator = :kaminari end after do ApiPagination.config.paginator = ENV['PAGINATOR'].to_sym end it 'should accept paginate_array_options option' do expect(Kaminari).to receive(:paginate_array) .with(collection, paginate_array_options) .and_call_original ApiPagination.paginate( collection, { per_page: 30, paginate_array_options: paginate_array_options } ) end end end api-pagination-4.2.0/spec/grape_spec.rb000066400000000000000000000103021264155566300200150ustar00rootroot00000000000000require 'spec_helper' require 'support/shared_examples/existing_headers' require 'support/shared_examples/first_page' require 'support/shared_examples/middle_page' require 'support/shared_examples/last_page' describe NumbersAPI do describe 'GET #index' do let(:links) { last_response.headers['Link'].split(', ') } let(:total) { last_response.headers['Total'].to_i } let(:per_page) { last_response.headers['Per-Page'].to_i } context 'without enough items to give more than one page' do before { get '/numbers', :count => 10 } it 'should not paginate' do expect(last_response.headers.keys).not_to include('Link') end it 'should give a Total header' do expect(total).to eq(10) end it 'should give a Per-Page header' do expect(per_page).to eq(10) end it 'should list all numbers in the response body' do body = '[1,2,3,4,5,6,7,8,9,10]' expect(last_response.body).to eq(body) end end context 'with existing Link headers' do before { get '/numbers', :count => 30, :with_headers => true } it_behaves_like 'an endpoint with existing Link headers' end context 'with enough items to paginate' do context 'when on the first page' do before { get '/numbers', :count => 100 } it_behaves_like 'an endpoint with a first page' end context 'when on the last page' do before { get '/numbers', :count => 100, :page => 10 } it_behaves_like 'an endpoint with a last page' end context 'when somewhere comfortably in the middle' do before { get '/numbers', :count => 100, :page => 2 } it_behaves_like 'an endpoint with a middle page' end context 'with a max_per_page setting' do before { get '/numbers', :count => 100, :per_page => 30 } it 'should not go above the max_per_page_limit' do body = '[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]' expect(last_response.body).to eq(body) end end end context 'with custom response headers' do before do ApiPagination.config.total_header = 'X-Total-Count' ApiPagination.config.per_page_header = 'X-Per-Page' ApiPagination.config.page_header = 'X-Page' get '/numbers', count: 10 end after do ApiPagination.config.total_header = 'Total' ApiPagination.config.per_page_header = 'Per-Page' ApiPagination.config.page_header = nil end let(:total) { last_response.header['X-Total-Count'].to_i } let(:per_page) { last_response.header['X-Per-Page'].to_i } let(:page) { last_response.header['X-Page'].to_i } it 'should give a X-Total-Count header' do headers_keys = last_response.headers.keys expect(headers_keys).not_to include('Total') expect(headers_keys).to include('X-Total-Count') expect(total).to eq(10) end it 'should give a X-Per-Page header' do headers_keys = last_response.headers.keys expect(headers_keys).not_to include('Per-Page') expect(headers_keys).to include('X-Per-Page') expect(per_page).to eq(10) end it 'should give a X-Page header' do headers_keys = last_response.headers.keys expect(headers_keys).to include('X-Page') expect(page).to eq(1) end end context 'configured not to include the total' do before { ApiPagination.config.include_total = false } it 'should not include a Total header' do get '/numbers', count: 10 expect(last_response.header['Total']).to be_nil end after { ApiPagination.config.include_total = true } end context 'with query string including array parameter' do before do get '/numbers', { count: 100, parity: ['odd', 'even']} end it 'returns links with with same received parameters' do expect(links).to include('; rel="last"') expect(links).to include('; rel="next"') end end end end api-pagination-4.2.0/spec/rails_spec.rb000066400000000000000000000072301264155566300200370ustar00rootroot00000000000000require 'spec_helper' require 'support/shared_examples/existing_headers' require 'support/shared_examples/first_page' require 'support/shared_examples/middle_page' require 'support/shared_examples/last_page' describe NumbersController, :type => :controller do before { request.host = 'example.org' } describe 'GET #index' do let(:links) { response.headers['Link'].split(', ') } let(:total) { response.headers['Total'].to_i } let(:per_page) { response.headers['Per-Page'].to_i } context 'without enough items to give more than one page' do before { get :index, :count => 10 } it 'should not paginate' do expect(response.headers.keys).not_to include('Link') end it 'should give a Total header' do expect(total).to eq(10) end it 'should give a Per-Page header' do expect(per_page).to eq(10) end it 'should list all numbers in the response body' do body = '[1,2,3,4,5,6,7,8,9,10]' expect(response.body).to eq(body) end end context 'with existing Link headers' do before { get :index, :count => 30, :with_headers => true } it_behaves_like 'an endpoint with existing Link headers' end context 'with enough items to paginate' do context 'when on the first page' do before { get :index, :count => 100 } it_behaves_like 'an endpoint with a first page' end context 'when on the last page' do before { get :index, :count => 100, :page => 10 } it_behaves_like 'an endpoint with a last page' end context 'when somewhere comfortably in the middle' do before { get :index, :count => 100, :page => 2 } it_behaves_like 'an endpoint with a middle page' end end context 'providing a block' do it 'yields to the block instead of implicitly rendering' do get :index_with_custom_render, :count => 100 json = { numbers: (1..10).map { |n| { number: n } } }.to_json expect(response.body).to eq(json) end end context 'with custom response headers' do before do ApiPagination.config.total_header = 'X-Total-Count' ApiPagination.config.per_page_header = 'X-Per-Page' ApiPagination.config.page_header = 'X-Page' get :index, count: 10 end after do ApiPagination.config.total_header = 'Total' ApiPagination.config.per_page_header = 'Per-Page' ApiPagination.config.page_header = nil end let(:total) { response.header['X-Total-Count'].to_i } let(:per_page) { response.header['X-Per-Page'].to_i } let(:page) { response.header['X-Page'].to_i } it 'should give a X-Total-Count header' do headers_keys = response.headers.keys expect(headers_keys).not_to include('Total') expect(headers_keys).to include('X-Total-Count') expect(total).to eq(10) end it 'should give a X-Per-Page header' do headers_keys = response.headers.keys expect(headers_keys).not_to include('Per-Page') expect(headers_keys).to include('X-Per-Page') expect(per_page).to eq(10) end it 'should give a X-Page header' do headers_keys = response.headers.keys expect(headers_keys).to include('X-Page') expect(page).to eq(1) end end context 'configured not to include the total' do before { ApiPagination.config.include_total = false } it 'should not include a Total header' do get :index, count: 10 expect(response.header['Total']).to be_nil end after { ApiPagination.config.include_total = true } end end end api-pagination-4.2.0/spec/sequel_spec.rb000066400000000000000000000011711264155566300202210ustar00rootroot00000000000000require 'spec_helper' if ApiPagination.config.paginator == :will_paginate require 'sqlite3' require 'sequel' require 'will_paginate/sequel' DB = Sequel.sqlite DB.extension :pagination DB.create_table :people do primary_key :id String :name end describe 'Using will_paginate with Sequel' do let(:people) do DB[:people] end before(:each) do people.insert(name: 'John') people.insert(name: 'Mary') end it 'returns a Sequel::Dataset' do collection = ApiPagination.paginate(people) expect(collection.kind_of?(Sequel::Dataset)).to be_truthy end end end api-pagination-4.2.0/spec/spec_helper.rb000066400000000000000000000016611264155566300202060ustar00rootroot00000000000000require 'support/numbers_controller' require 'support/numbers_api' require 'api-pagination' if ENV['PAGINATOR'] require ENV['PAGINATOR'] ApiPagination.config.paginator = ENV['PAGINATOR'].to_sym else warn 'No PAGINATOR set. Defaulting to kaminari. To test against will_paginate, run `PAGINATOR=will_paginate bundle exec rspec`' require 'kaminari' ApiPagination.config.paginator = :kaminari end require 'will_paginate/array' RSpec.configure do |config| config.include Rack::Test::Methods config.include ControllerExampleGroup, :type => :controller # Disable the 'should' syntax. config.expect_with :rspec do |c| c.syntax = :expect end # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = 'random' def app NumbersAPI end end api-pagination-4.2.0/spec/support/000077500000000000000000000000001264155566300171005ustar00rootroot00000000000000api-pagination-4.2.0/spec/support/numbers_api.rb000066400000000000000000000011421264155566300217270ustar00rootroot00000000000000require 'grape' require 'api-pagination' class NumbersAPI < Grape::API format :json desc 'Return some paginated set of numbers' paginate :per_page => 10, :max_per_page => 25 params do requires :count, :type => Integer optional :with_headers, :default => false, :type => Boolean end get :numbers do if params[:with_headers] url = request.url.sub(/\?.*/, '') query = Rack::Utils.parse_query(request.query_string) query.delete('with_headers') header 'Link', %(<#{url}?#{query.to_query}>; rel="without") end paginate (1..params[:count]).to_a end end api-pagination-4.2.0/spec/support/numbers_controller.rb000066400000000000000000000034351264155566300233500ustar00rootroot00000000000000require 'action_controller' require 'ostruct' module Rails def self.application @application ||= begin routes = ActionDispatch::Routing::RouteSet.new OpenStruct.new(:routes => routes, :env_config => {}) end end end module ControllerExampleGroup def self.included(base) base.extend ClassMethods base.send(:include, ActionController::TestCase::Behavior) base.prepend_before do @routes = Rails.application.routes @controller = described_class.new end end module ClassMethods def setup(*methods) methods.each do |method| if method.to_s =~ /^setup_(fixtures|controller_request_and_response)$/ prepend_before { send method } else before { send method } end end end def teardown(*methods) methods.each { |method| after { send method } } end end end Rails.application.routes.draw do resources :numbers, :only => [:index] do get :index_with_custom_render, on: :collection end end class NumbersSerializer def initialize(numbers) @numbers = numbers end def to_json(options = {}) { numbers: @numbers.map { |n| { number: n } } }.to_json end end class NumbersController < ActionController::Base include Rails.application.routes.url_helpers def index total = params.fetch(:count).to_i if params[:with_headers] query = request.query_parameters.dup query.delete(:with_headers) headers['Link'] = %(<#{numbers_url}?#{query.to_param}>; rel="without") end paginate :json => (1..total).to_a, :per_page => 10 end def index_with_custom_render total = params.fetch(:count).to_i numbers = (1..total).to_a numbers = paginate numbers, :per_page => 10 render json: NumbersSerializer.new(numbers) end end api-pagination-4.2.0/spec/support/shared_examples/000077500000000000000000000000001264155566300222445ustar00rootroot00000000000000api-pagination-4.2.0/spec/support/shared_examples/existing_headers.rb000066400000000000000000000010171264155566300261150ustar00rootroot00000000000000shared_examples 'an endpoint with existing Link headers' do it 'should keep existing Links' do expect(links).to include('; rel="without"') end it 'should contain pagination Links' do expect(links).to include('; rel="next"') expect(links).to include('; rel="last"') end it 'should give a Total header' do expect(total).to eq(30) end end api-pagination-4.2.0/spec/support/shared_examples/first_page.rb000066400000000000000000000015341264155566300247170ustar00rootroot00000000000000shared_examples 'an endpoint with a first page' do it 'should not give a link with rel "first"' do expect(links).not_to include('rel="first"') end it 'should not give a link with rel "prev"' do expect(links).not_to include('rel="prev"') end it 'should give a link with rel "last"' do expect(links).to include('; rel="last"') end it 'should give a link with rel "next"' do expect(links).to include('; rel="next"') end it 'should give a Total header' do expect(total).to eq(100) end it 'should list the first page of numbers in the response body' do body = '[1,2,3,4,5,6,7,8,9,10]' if defined?(response) expect(response.body).to eq(body) else expect(last_response.body).to eq(body) end end end api-pagination-4.2.0/spec/support/shared_examples/last_page.rb000066400000000000000000000015441264155566300245340ustar00rootroot00000000000000shared_examples 'an endpoint with a last page' do it 'should not give a link with rel "last"' do expect(links).not_to include('rel="last"') end it 'should not give a link with rel "next"' do expect(links).not_to include('rel="next"') end it 'should give a link with rel "first"' do expect(links).to include('; rel="first"') end it 'should give a link with rel "prev"' do expect(links).to include('; rel="prev"') end it 'should give a Total header' do expect(total).to eq(100) end it 'should list the last page of numbers in the response body' do body = '[91,92,93,94,95,96,97,98,99,100]' if defined?(response) expect(response.body).to eq(body) else expect(last_response.body).to eq(body) end end end api-pagination-4.2.0/spec/support/shared_examples/middle_page.rb000066400000000000000000000014251264155566300250250ustar00rootroot00000000000000shared_examples 'an endpoint with a middle page' do it 'should give all pagination links' do expect(links).to include('; rel="first"') expect(links).to include('; rel="last"') expect(links).to include('; rel="next"') expect(links).to include('; rel="prev"') end it 'should give a Total header' do expect(total).to eq(100) end it 'should list a middle page of numbers in the response body' do body = '[11,12,13,14,15,16,17,18,19,20]' if defined?(response) expect(response.body).to eq(body) else expect(last_response.body).to eq(body) end end end