Skip to content

Commit 87c47f8

Browse files
committed
Merge pull request #1041 from bacarini/master
Adding pagination links
2 parents fc7b9c3 + d50262e commit 87c47f8

File tree

13 files changed

+614
-181
lines changed

13 files changed

+614
-181
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ test/version_tmp
1919
tmp
2020
*.swp
2121
.ruby-version
22+
.ruby-gemset

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
* adds cache support to attributes and associations [@joaomdmoura]
1010
* uses model name to determine the type [@lsylvester]
1111
* remove root key option and split JSON adapter [@joaomdmoura]
12-
* adds FlattenJSON as default adapter [@joaomdmoura]
12+
* adds FlattenJSON as default adapter [@joaomdmoura]
13+
* adds support for `pagination links` at top level of JsonApi adapter [@bacarini]

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ If you wish to use a serializer other than the default, you can explicitly pass
118118
render json: @posts, each_serializer: PostPreviewSerializer
119119

120120
# Or, you can explicitly provide the collection serializer as well
121-
render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer
121+
render json: @posts, serializer: CollectionSerializer, each_serializer: PostPreviewSerializer
122122
```
123123

124124
### Meta
@@ -272,6 +272,11 @@ And you can change the JSON key that the serializer should use for a particular
272272

273273
The `url` declaration describes which named routes to use while generating URLs
274274
for your JSON. Not every adapter will require URLs.
275+
## Pagination
276+
277+
Pagination links will be included in your response automatically as long as the resource is paginated using [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate) and if you are using a ```JSON-API``` adapter.
278+
279+
Although the others adapters does not have this feature, it is possible to implement pagination links to `JSON` adapter. For more information about it, please see in our docs [How to add pagination links](https://github.com/rails-api/active_model_serializers/blob/master/docs/howto/add_pagination_links.md)
275280

276281
## Caching
277282

active_model_serializers.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
2424
spec.add_development_dependency "bundler", "~> 1.6"
2525
spec.add_development_dependency "timecop", ">= 0.7"
2626
spec.add_development_dependency "rake"
27+
spec.add_development_dependency "kaminari"
28+
spec.add_development_dependency "will_paginate"
2729
end

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This is the documentation of AMS, it's focused on the **0.10.x version.**
1212
## How to
1313

1414
- [How to add root key](howto/add_root_key.md)
15+
- [How to add pagination links](howto/add_pagination_links.md)
1516

1617
## Getting Help
1718

docs/howto/add_pagination_links.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# How to add pagination links
2+
3+
### JSON-API adapter
4+
5+
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.
6+
7+
If you want pagination links in your response, use [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate).
8+
9+
###### Kaminari examples
10+
```ruby
11+
#array
12+
@posts = Kaminari.paginate_array([1, 2, 3]).page(3).per(1)
13+
render json: @posts
14+
15+
#active_record
16+
@posts = Post.page(3).per(1)
17+
render json: @posts
18+
```
19+
20+
###### WillPaginate examples
21+
22+
```ruby
23+
#array
24+
@posts = [1,2,3].paginate(page: 3, per_page: 1)
25+
render json: @posts
26+
27+
#active_record
28+
@posts = Post.page(3).per_page(1)
29+
render json: @posts
30+
```
31+
32+
```ruby
33+
ActiveModel::Serializer.config.adapter = :json_api
34+
```
35+
36+
ex:
37+
```json
38+
{
39+
"data": [
40+
{
41+
"type": "articles",
42+
"id": "3",
43+
"attributes": {
44+
"title": "JSON API paints my bikeshed!",
45+
"body": "The shortest article. Ever.",
46+
"created": "2015-05-22T14:56:29.000Z",
47+
"updated": "2015-05-22T14:56:28.000Z"
48+
}
49+
}
50+
],
51+
"links": {
52+
"self": "http://example.com/articles?page[number]=3&page[size]=1",
53+
"first": "http://example.com/articles?page[number]=1&page[size]=1",
54+
"prev": "http://example.com/articles?page[number]=2&page[size]=1",
55+
"next": "http://example.com/articles?page[number]=4&page[size]=1",
56+
"last": "http://example.com/articles?page[number]=13&page[size]=1"
57+
}
58+
}
59+
```
60+
61+
AMS pagination relies on a paginated collection with the methods `current_page`, `total_pages`, and `size`, such as are supported by both [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate).
62+
63+
64+
### JSON adapter
65+
66+
If you are using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key.
67+
68+
In your action specify a custom serializer.
69+
```ruby
70+
render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer
71+
```
72+
73+
And then, you could do something like the following class.
74+
```ruby
75+
class PaginatedSerializer < ActiveModel::Serializer::ArraySerializer
76+
def initialize(object, options={})
77+
meta_key = options[:meta_key] || :meta
78+
options[meta_key] ||= {}
79+
options[meta_key] = {
80+
current_page: object.current_page,
81+
next_page: object.next_page,
82+
prev_page: object.prev_page,
83+
total_pages: object.total_pages,
84+
total_count: object.total_count
85+
}
86+
super(object, options)
87+
end
88+
end
89+
```
90+
ex.
91+
```json
92+
{
93+
"articles": [
94+
{
95+
"id": 2,
96+
"title": "JSON API paints my bikeshed!",
97+
"body": "The shortest article. Ever."
98+
}
99+
],
100+
"meta": {
101+
"current_page": 3,
102+
"next_page": 4,
103+
"prev_page": 2,
104+
"total_pages": 10,
105+
"total_count": 10
106+
}
107+
}
108+
```
109+
110+
### FlattenJSON adapter
111+
112+
This adapter does not allow us to use `meta` key, due to that it is not possible to add pagination links.

lib/action_controller/serialization.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def use_adapter?
4747

4848
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
4949
define_method renderer_method do |resource, options|
50+
options.fetch(:context) { options[:context] = request }
5051
serializable_resource = get_serializer(resource, options)
5152
super(serializable_resource, options)
5253
end

lib/active_model/serializer/adapter/json_api.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'active_model/serializer/adapter/json_api/fragment_cache'
2+
require 'active_model/serializer/adapter/json_api/pagination_links'
23

34
module ActiveModel
45
class Serializer
@@ -27,6 +28,8 @@ def serializable_hash(options = nil)
2728
@hash[:included] |= result[:included]
2829
end
2930
end
31+
32+
add_links(options)
3033
else
3134
@hash[:data] = attributes_for_serializer(serializer, options)
3235
add_resource_relationships(@hash[:data], serializer)
@@ -157,6 +160,23 @@ def add_resource_relationships(attrs, serializer, options = {})
157160
end
158161
end
159162
end
163+
164+
def add_links(options)
165+
links = @hash.fetch(:links) { {} }
166+
resources = serializer.instance_variable_get(:@resource)
167+
@hash[:links] = add_pagination_links(links, resources, options) if is_paginated?(resources)
168+
end
169+
170+
def add_pagination_links(links, resources, options)
171+
pagination_links = JsonApi::PaginationLinks.new(resources, options[:context]).serializable_hash(options)
172+
links.update(pagination_links)
173+
end
174+
175+
def is_paginated?(resource)
176+
resource.respond_to?(:current_page) &&
177+
resource.respond_to?(:total_pages) &&
178+
resource.respond_to?(:size)
179+
end
160180
end
161181
end
162182
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module ActiveModel
2+
class Serializer
3+
class Adapter
4+
class JsonApi < Adapter
5+
class PaginationLinks
6+
FIRST_PAGE = 1
7+
8+
attr_reader :collection, :context
9+
10+
def initialize(collection, context)
11+
@collection = collection
12+
@context = context
13+
end
14+
15+
def serializable_hash(options = {})
16+
pages_from.each_with_object({}) do |(key, value), hash|
17+
params = query_parameters.merge(page: { number: value, size: collection.size }).to_query
18+
19+
hash[key] = "#{url(options)}?#{params}"
20+
end
21+
end
22+
23+
private
24+
25+
def pages_from
26+
return {} if collection.total_pages == FIRST_PAGE
27+
28+
{}.tap do |pages|
29+
pages[:self] = collection.current_page
30+
31+
unless collection.current_page == FIRST_PAGE
32+
pages[:first] = FIRST_PAGE
33+
pages[:prev] = collection.current_page - FIRST_PAGE
34+
end
35+
36+
unless collection.current_page == collection.total_pages
37+
pages[:next] = collection.current_page + FIRST_PAGE
38+
pages[:last] = collection.total_pages
39+
end
40+
end
41+
end
42+
43+
def url(options)
44+
@url ||= options.fetch(:links, {}).fetch(:self, nil) || original_url
45+
end
46+
47+
def original_url
48+
@original_url ||= context.original_url[/\A[^?]+/]
49+
end
50+
51+
def query_parameters
52+
@query_parameters ||= context.query_parameters
53+
end
54+
end
55+
end
56+
end
57+
end
58+
end

0 commit comments

Comments
 (0)