Skip to content

Commit 81ec702

Browse files
committed
Change interface for Rails pagination
This fixes #7 but is also related to a more dire issue. `after_filter` was misunderstood to provide access to the response before the body had been written. In actuality, the response has already been fully constructed by the time `after_filter` is called, which means that the collection has already been queried and written as json (or XML). In order to maintain a nice interface without using after_filter, the method signature of `paginate` has changed to act like `render`: ```rails class API::v1::PostsController < ApplicationController def index # Uncomment this line to change the default per_page from 10 to 25: # params[:per_page] ||= 25 posts = Post.all paginate json: posts end end ``` At the end, `paginate` simply passes its arguments on to `render` so all of `render's` options are supported. The collection is assumed to live either in the argument hash's `:json` key or an `:xml` key. Signed-off-by: David Celis <[email protected]>
1 parent aa7b000 commit 81ec702

File tree

3 files changed

+30
-21
lines changed

3 files changed

+30
-21
lines changed

README.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,41 @@ gem 'api-pagination'
2424

2525
## Rails
2626

27-
In your controller:
27+
In your controller, provide a pageable collection to the `paginate` method:
2828

2929
```ruby
3030
class MoviesController < ApplicationController
31-
# Uses the @movies and @actors variables set below.
32-
# This method must take an ActiveRecord::Relation
33-
# or some equivalent pageable set.
34-
after_filter only: [:index] { paginate(:movies) }
35-
after_filter only: [:cast] { paginate(:actors) }
36-
3731
# GET /movies
3832
def index
39-
@movies = Movie.all # Movie.scoped if using ActiveRecord 3.x
33+
movies = Movie.all # Movie.scoped if using ActiveRecord 3.x
4034

41-
render json: @movies
35+
paginate json: movies
4236
end
4337

4438
# GET /movies/:id/cast
4539
def cast
46-
@movie = Movie.find(params[:id])
47-
@actors = @movie.actors
40+
actors = Movie.find(params[:id]).actors
4841

49-
# Override how many Actors get returned.
50-
params[:per_page] = 10
42+
# Override how many Actors get returned. The default is 10.
43+
params[:per_page] = 25
5144

52-
render json: @actors
45+
paginate json: actors
5346
end
5447
end
5548
```
5649

50+
`paginate` will:
51+
52+
* Pull your collection from `json:` or `xml:`
53+
* Use `params[:page]` and `params[:per_page]` to paginate your collection for you
54+
* Use the paginated collection to render `Link` headers
55+
* Call `ActionController::Base#render` with whatever you passed to `paginate`.
56+
57+
The collection sent to `paginate` _must_ respond to your paginator's methods. For Kaminari, `Kaminari.paginate_array` will be called for you behind-the-scenes. For WillPaginate, you're out of luck unless you somewhere `require 'will_paginate/array'`. Because this pollutes `Array`, it won't be done for you automatically.
58+
5759
## Grape
5860

59-
In your API endpoint:
61+
Grape is similar, though `paginate` won't take options. Only your collection. In your API endpoint:
6062

6163
```ruby
6264
class MoviesAPI < Grape::API

lib/rails/pagination.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ module Rails
22
module Pagination
33
protected
44

5-
def paginate(collection)
6-
collection = instance_variable_get(:"@#{collection}")
5+
def paginate(options)
6+
collection = options[:json] || options[:xml]
77

88
block = Proc.new do |collection|
99
links = (headers['Link'] || "").split(',').map(&:strip)
@@ -19,7 +19,13 @@ def paginate(collection)
1919
headers['Total'] = ApiPagination.total_from(collection)
2020
end
2121

22-
ApiPagination.paginate(collection, params, &block)
22+
collection = ApiPagination.paginate(collection, params, &block)
23+
24+
options[:json] = collection if options[:json]
25+
options[:xml] = collection if options[:xml]
26+
27+
render options
2328
end
2429
end
2530
end
31+

spec/support/numbers_controller.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def teardown(*methods)
4545
class NumbersController < ActionController::Base
4646
include Rails.application.routes.url_helpers
4747

48-
after_filter :only => [:index] { paginate(:numbers) }
48+
# after_filter :only => [:index] { paginate(:numbers) }
4949

5050
def index
5151
page = params.fetch(:page, 1).to_i
@@ -57,7 +57,8 @@ def index
5757
headers['Link'] = %(<#{numbers_url}?#{query.to_param}>; rel="without")
5858
end
5959

60-
@numbers = (1..total).to_a
61-
render :json => @numbers
60+
numbers = (1..total).to_a
61+
62+
paginate :json => numbers
6263
end
6364
end

0 commit comments

Comments
 (0)