Skip to content

Commit 8139f64

Browse files
committed
Add support for Grape
Signed-off-by: David Celis <[email protected]>
1 parent ae4773a commit 8139f64

File tree

8 files changed

+186
-17
lines changed

8 files changed

+186
-17
lines changed

api-pagination.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Gem::Specification.new do |s|
1616
s.test_files = Dir['spec/**/*']
1717
s.require_paths = ['lib']
1818

19-
s.add_development_dependency 'actionpack'
2019
s.add_development_dependency 'rspec'
20+
s.add_development_dependency 'grape'
21+
s.add_development_dependency 'actionpack'
2122
end

lib/api-pagination/hooks.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ def self.init
1313
ActionController::API.send(:include, Rails::Pagination)
1414
end
1515

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+
1622
begin; require 'will_paginate'; rescue LoadError; end
1723
if defined?(WillPaginate::CollectionMethod)
1824
WillPaginate::CollectionMethods.module_eval do

lib/grape/pagination.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module Grape
2+
module Pagination
3+
def self.included(base)
4+
Grape::Endpoint.class_eval do
5+
def paginate(collection)
6+
collection.page(params[:page]).per(params[:per_page]).tap do |scope|
7+
links = (header['Link'] || "").split(',').map(&:strip)
8+
9+
url = request.url.sub(/\?.*$/, '')
10+
pages = {}
11+
12+
unless scope.first_page?
13+
pages[:first] = 1
14+
pages[:prev] = scope.current_page - 1
15+
end
16+
17+
unless scope.last_page?
18+
pages[:last] = scope.total_pages
19+
pages[:next] = scope.current_page + 1
20+
end
21+
22+
pages.each do |k, v|
23+
old_params = Rack::Utils.parse_query(request.query_string)
24+
new_params = old_params.merge('page' => v)
25+
links << %(<#{url}?#{new_params.to_param}>; rel="#{k}")
26+
end
27+
28+
header 'Link', links.join(', ') unless links.empty?
29+
end
30+
end
31+
end
32+
33+
base.class_eval do
34+
def self.paginate(options = {})
35+
options.reverse_merge!(per_page: 10)
36+
params do
37+
optional :page, type: Integer, default: 1,
38+
desc: 'Page offset to fetch.'
39+
optional :per_page, type: Integer, default: options[:per_page],
40+
desc: 'Number of results to return per page.'
41+
end
42+
end
43+
end
44+
end
45+
end
46+
end

spec/grape_spec.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'spec_helper'
2+
3+
describe NumbersAPI do
4+
describe 'GET #index' do
5+
context 'without enough items to give more than one page' do
6+
it 'should not paginate' do
7+
get :numbers, count: 20
8+
expect(last_response.headers.keys).not_to include('Link')
9+
end
10+
end
11+
12+
context 'with existing Link headers' do
13+
before { get :numbers, count: 30, with_headers: true }
14+
let(:links) { last_response.headers['Link'].split(', ') }
15+
16+
it 'should keep existing Links' do
17+
expect(links).to include('<http://example.org/numbers?count=30>; rel="without"')
18+
end
19+
20+
it 'should contain pagination Links' do
21+
expect(links).to include('<http://example.org/numbers?count=30&page=2&with_headers=true>; rel="next"')
22+
expect(links).to include('<http://example.org/numbers?count=30&page=2&with_headers=true>; rel="last"')
23+
end
24+
end
25+
26+
context 'with enough items to paginate' do
27+
context 'when on the first page' do
28+
before { get :numbers, :count => 100 }
29+
let(:links) { last_response.headers['Link'].split(', ') }
30+
31+
it 'should not give a link with rel "first"' do
32+
expect(links).not_to include('rel="first"')
33+
end
34+
35+
it 'should not give a link with rel "prev"' do
36+
expect(links).not_to include('rel="prev"')
37+
end
38+
39+
it 'should give a link with rel "last"' do
40+
expect(links).to include('<http://example.org/numbers?count=100&page=4>; rel="last"')
41+
end
42+
43+
it 'should give a link with rel "next"' do
44+
expect(links).to include('<http://example.org/numbers?count=100&page=2>; rel="next"')
45+
end
46+
end
47+
48+
context 'when on the last page' do
49+
before { get :numbers, :count => 100, :page => 4 }
50+
let(:links) { last_response.headers['Link'].split(', ') }
51+
52+
it 'should not give a link with rel "last"' do
53+
expect(links).not_to include('rel="last"')
54+
end
55+
56+
it 'should not give a link with rel "next"' do
57+
expect(links).not_to include('rel="next"')
58+
end
59+
60+
it 'should give a link with rel "first"' do
61+
expect(links).to include('<http://example.org/numbers?count=100&page=1>; rel="first"')
62+
end
63+
64+
it 'should give a link with rel "prev"' do
65+
expect(links).to include('<http://example.org/numbers?count=100&page=3>; rel="prev"')
66+
end
67+
end
68+
69+
context 'when somewhere comfortably in the middle' do
70+
it 'should give all pagination links' do
71+
get :numbers, count: 100, page: 2
72+
73+
links = last_response.headers['Link'].split(', ')
74+
75+
expect(links).to include('<http://example.org/numbers?count=100&page=1>; rel="first"')
76+
expect(links).to include('<http://example.org/numbers?count=100&page=4>; rel="last"')
77+
expect(links).to include('<http://example.org/numbers?count=100&page=3>; rel="next"')
78+
expect(links).to include('<http://example.org/numbers?count=100&page=1>; rel="prev"')
79+
end
80+
end
81+
end
82+
end
83+
end

spec/rails_spec.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require 'rails_spec_helper'
1+
require 'spec_helper'
22

33
describe NumbersController, :type => :controller do
44
describe 'GET #index' do
@@ -10,8 +10,9 @@
1010
end
1111

1212
context 'with existing Link headers' do
13-
before(:each) do
13+
before do
1414
get :index, count: 30, with_headers: true
15+
1516
@links = response.headers['Link'].split(', ')
1617
end
1718

@@ -27,7 +28,7 @@
2728

2829
context 'with enough items to paginate' do
2930
context 'when on the first page' do
30-
before(:each) do
31+
before do
3132
get :index, count: 100
3233

3334
@links = response.headers['Link'].split(', ')
@@ -51,7 +52,7 @@
5152
end
5253

5354
context 'when on the last page' do
54-
before(:each) do
55+
before do
5556
get :index, count: 100, page: 4
5657

5758
@links = response.headers['Link'].split(', ')

spec/spec_helper.rb

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
1-
require 'api-pagination'
21
require 'rspec/autorun'
3-
require 'ostruct'
2+
require 'support/numbers_controller'
3+
require 'api-pagination'
4+
require 'support/numbers_api'
45

56
# Quacks like Kaminari
6-
PaginatedSet = Struct.new(:current_page, :total_count) do
7-
def limit_value() 25 end
8-
7+
PaginatedSet = Struct.new(:current_page, :per_page, :total_count) do
98
def total_pages
10-
total_count.zero? ? 1 : (total_count.to_f / limit_value).ceil
9+
total_count.zero? ? 1 : (total_count.to_f / per_page).ceil
1110
end
1211

1312
def first_page?() current_page == 1 end
1413
def last_page?() current_page == total_pages end
14+
15+
def page(page)
16+
current_page = page
17+
self
18+
end
19+
20+
def per(per)
21+
per_page = per
22+
self
23+
end
1524
end
1625

1726
RSpec.configure do |config|
27+
config.include Rack::Test::Methods
28+
config.include ControllerExampleGroup, :type => :controller
29+
1830
# Disable the 'should' syntax.
1931
config.expect_with :rspec do |c|
2032
c.syntax = :expect
@@ -25,4 +37,8 @@ def last_page?() current_page == total_pages end
2537
# the seed, which is printed after each run.
2638
# --seed 1234
2739
config.order = 'random'
40+
41+
def app
42+
NumbersAPI
43+
end
2844
end

spec/support/numbers_api.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class NumbersAPI < Grape::API
2+
format :json
3+
4+
desc 'Return some paginated set of numbers'
5+
paginate :per_page => 25
6+
params do
7+
requires :count, :type => Integer
8+
optional :with_headers, :default => false, :type => Boolean
9+
end
10+
get :numbers do
11+
if params[:with_headers]
12+
url = request.url.sub(/\?.*/, '')
13+
query = Rack::Utils.parse_query(request.query_string)
14+
query.delete('with_headers')
15+
header 'Link', %(<#{url}?#{query.to_query}>; rel="without")
16+
end
17+
18+
paginate PaginatedSet.new(params[:page], params[:per_page], params[:count])
19+
end
20+
end

spec/rails_spec_helper.rb renamed to spec/support/numbers_controller.rb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require 'action_controller'
2-
require 'spec_helper'
2+
require 'ostruct'
33

44
module Rails
55
def self.application
@@ -57,11 +57,7 @@ def index
5757
headers['Link'] = %(<#{numbers_url}?#{query.to_param}>; rel="without") if params[:with_headers]
5858
end
5959

60-
@numbers = PaginatedSet.new(page, total)
60+
@numbers = PaginatedSet.new(page, 25, total)
6161
render json: @numbers
6262
end
6363
end
64-
65-
RSpec.configure do |config|
66-
config.include ControllerExampleGroup, :type => :controller
67-
end

0 commit comments

Comments
 (0)