-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pagination Links #1041
Pagination Links #1041
Changes from 18 commits
f7c77c1
b864302
1fe8b06
e040d6f
331218d
acb6545
36c452e
e62a7d6
7be25fe
e0d050d
77a8f66
59ae84b
a41d90c
2c2f948
5031eb9
01eab3b
f85027e
3c3578a
b73ffe2
d50262e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,3 +19,4 @@ test/version_tmp | |
tmp | ||
*.swp | ||
.ruby-version | ||
.ruby-gemset |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# How to add pagination links | ||
|
||
### JSON-API adapter | ||
|
||
Pagination links will be included in your response automatically as long as the resource is paginated and if you are using a ```JSON-API``` adapter. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But you could use the PaginationLinks builder with the JSON adapter right? like if I wanted to put links in the 'meta' attribute using a subclass of the json adapter? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, PaginationLinks only will be included to Ex. render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer class PaginatedSerializer < ActiveModel::Serializer::ArraySerializer
def initialize(object, options={})
meta_key = options[:meta_key] || :meta
options[meta_key] ||= {}
options[meta_key] = {
current_page: object.current_page,
next_page: object.next_page,
prev_page: object.prev_page,
total_pages: object.total_pages,
total_count: object.total_count
}
super(object, options)
end
end There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Winning! That in the docs? (I'm in a car now)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is not! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Up to you, but I think it could be linked from the CollectionSerializer above https://github.com/rails-api/active_model_serializers/pull/1041/files#diff-04c6e90faac2675aa89e2176d2eec7d8R121 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 yup, this would be good to have it, maybe a new article on the docs 😄 |
||
If you want pagination links in your response, use [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate). | ||
|
||
```ruby | ||
#kaminari example | ||
@posts = Kaminari.paginate_array(Post.all).page(3).per(1) | ||
render json: @posts | ||
|
||
#will_paginate example | ||
@posts = Post.all.paginate(page: 3, per_page: 1) | ||
render json: @posts | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bacarini would you mind updating the will_paginate example to use the 'newer, recommended' syntax that doesn't require the deprecated finders?
Or even
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey, @bf4 tks for the review. I believe that, using
What do you think doing this way? Kaminari examples#array
@posts = Kaminari.paginate_array([1, 2, 3]).page(3).per(1)
render json: @posts
#active_record
@posts = Post.page(3).per(1)
render json: @posts WillPaginate examples#array
@posts = [1,2,3].paginate(page: 3, per_page: 1)
render json: @posts
#active_record
@posts = Post.page(3).per_page(1)
render json: @posts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point
|
||
|
||
```ruby | ||
ActiveModel::Serializer.config.adapter = :json_api | ||
``` | ||
|
||
ex: | ||
```json | ||
{ | ||
"data": [ | ||
{ | ||
"type": "articles", | ||
"id": "3", | ||
"attributes": { | ||
"title": "JSON API paints my bikeshed!", | ||
"body": "The shortest article. Ever.", | ||
"created": "2015-05-22T14:56:29.000Z", | ||
"updated": "2015-05-22T14:56:28.000Z" | ||
} | ||
} | ||
], | ||
"links": { | ||
"self": "http://example.com/articles?page[number]=3&page[size]=1", | ||
"first": "http://example.com/articles?page[number]=1&page[size]=1", | ||
"prev": "http://example.com/articles?page[number]=2&page[size]=1", | ||
"next": "http://example.com/articles?page[number]=4&page[size]=1", | ||
"last": "http://example.com/articles?page[number]=13&page[size]=1" | ||
} | ||
} | ||
``` | ||
|
||
AMS relies on either [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate). Please install either dependency by adding one of those to your Gemfile. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: AMS relies on either Kaminari or WillPaginate for pagination. alternative to consider: AMS pagination relies on a paginated collection with the methods (links left out for ease of me typing :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
### JSON adapter | ||
|
||
If you are using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key. | ||
|
||
In your action specify a custom serializer. | ||
```ruby | ||
render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer | ||
``` | ||
|
||
And then, you could do something like the following class. | ||
```ruby | ||
class PaginatedSerializer < ActiveModel::Serializer::ArraySerializer | ||
def initialize(object, options={}) | ||
meta_key = options[:meta_key] || :meta | ||
options[meta_key] ||= {} | ||
options[meta_key] = { | ||
current_page: object.current_page, | ||
next_page: object.next_page, | ||
prev_page: object.prev_page, | ||
total_pages: object.total_pages, | ||
total_count: object.total_count | ||
} | ||
super(object, options) | ||
end | ||
end | ||
``` | ||
ex. | ||
```json | ||
{ | ||
"articles": [ | ||
{ | ||
"id": 2, | ||
"title": "JSON API paints my bikeshed!", | ||
"body": "The shortest article. Ever." | ||
} | ||
], | ||
"meta": { | ||
"current_page": 3, | ||
"next_page": 4, | ||
"prev_page": 2, | ||
"total_pages": 10, | ||
"total_count": 10 | ||
} | ||
} | ||
``` | ||
|
||
### FlattenJSON adapter | ||
|
||
This adapter does not allow us to use `meta` key, due to that it is not possible to add pagination links. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
require 'active_model/serializer/adapter/json_api/fragment_cache' | ||
require 'active_model/serializer/adapter/json_api/pagination_links' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
module ActiveModel | ||
class Serializer | ||
|
@@ -27,6 +28,8 @@ def serializable_hash(options = nil) | |
@hash[:included] |= result[:included] | ||
end | ||
end | ||
|
||
add_links(options) | ||
else | ||
@hash[:data] = attributes_for_serializer(serializer, options) | ||
add_resource_relationships(@hash[:data], serializer) | ||
|
@@ -157,6 +160,23 @@ def add_resource_relationships(attrs, serializer, options = {}) | |
end | ||
end | ||
end | ||
|
||
def add_links(options) | ||
links = @hash.fetch(:links) { {} } | ||
resources = serializer.instance_variable_get(:@resource) | ||
@hash[:links] = add_pagination_links(links, resources, options) if is_paginated?(resources) | ||
end | ||
|
||
def add_pagination_links(links, resources, options) | ||
pagination_links = JsonApi::PaginationLinks.new(resources, options[:context]).serializable_hash(options) | ||
links.update(pagination_links) | ||
end | ||
|
||
def is_paginated?(resource) | ||
resource.respond_to?(:current_page) && | ||
resource.respond_to?(:total_pages) && | ||
resource.respond_to?(:size) | ||
end | ||
end | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
module ActiveModel | ||
class Serializer | ||
class Adapter | ||
class JsonApi < Adapter | ||
class PaginationLinks | ||
FIRST_PAGE = 1 | ||
|
||
attr_reader :collection, :context | ||
|
||
def initialize(collection, context) | ||
@collection = collection | ||
@context = context | ||
end | ||
|
||
def serializable_hash(options = {}) | ||
pages_from.each_with_object({}) do |(key, value), hash| | ||
params = query_parameters.merge(page: { number: value, size: collection.size }).to_query | ||
|
||
hash[key] = "#{url(options)}?#{params}" | ||
end | ||
end | ||
|
||
private | ||
|
||
def pages_from | ||
return {} if collection.total_pages == FIRST_PAGE | ||
|
||
{}.tap do |pages| | ||
pages[:self] = collection.current_page | ||
|
||
unless collection.current_page == FIRST_PAGE | ||
pages[:first] = FIRST_PAGE | ||
pages[:prev] = collection.current_page - FIRST_PAGE | ||
end | ||
|
||
unless collection.current_page == collection.total_pages | ||
pages[:next] = collection.current_page + FIRST_PAGE | ||
pages[:last] = collection.total_pages | ||
end | ||
end | ||
end | ||
|
||
def url(options) | ||
@url ||= options.fetch(:links, {}).fetch(:self, nil) || original_url | ||
end | ||
|
||
def original_url | ||
@original_url ||= context.original_url[/\A[^?]+/] | ||
end | ||
|
||
def query_parameters | ||
@query_parameters ||= context.query_parameters | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍