Skip to content

Commit

Permalink
Merge pull request #1041 from bacarini/master
Browse files Browse the repository at this point in the history
Adding pagination links
  • Loading branch information
joaomdmoura committed Aug 22, 2015
2 parents fc7b9c3 + d50262e commit 87c47f8
Show file tree
Hide file tree
Showing 13 changed files with 614 additions and 181 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ test/version_tmp
tmp
*.swp
.ruby-version
.ruby-gemset
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
* adds cache support to attributes and associations [@joaomdmoura]
* uses model name to determine the type [@lsylvester]
* remove root key option and split JSON adapter [@joaomdmoura]
* adds FlattenJSON as default adapter [@joaomdmoura]
* adds FlattenJSON as default adapter [@joaomdmoura]
* adds support for `pagination links` at top level of JsonApi adapter [@bacarini]
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ If you wish to use a serializer other than the default, you can explicitly pass
render json: @posts, each_serializer: PostPreviewSerializer

# Or, you can explicitly provide the collection serializer as well
render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer
render json: @posts, serializer: CollectionSerializer, each_serializer: PostPreviewSerializer
```

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

The `url` declaration describes which named routes to use while generating URLs
for your JSON. Not every adapter will require URLs.
## Pagination

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.

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)

## Caching

Expand Down
2 changes: 2 additions & 0 deletions active_model_serializers.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "bundler", "~> 1.6"
spec.add_development_dependency "timecop", ">= 0.7"
spec.add_development_dependency "rake"
spec.add_development_dependency "kaminari"
spec.add_development_dependency "will_paginate"
end
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This is the documentation of AMS, it's focused on the **0.10.x version.**
## How to

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

## Getting Help

Expand Down
112 changes: 112 additions & 0 deletions docs/howto/add_pagination_links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 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.

If you want pagination links in your response, use [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate).

###### Kaminari examples
```ruby
#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

```ruby
#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
```

```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 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).


### 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.
1 change: 1 addition & 0 deletions lib/action_controller/serialization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def use_adapter?

[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
define_method renderer_method do |resource, options|
options.fetch(:context) { options[:context] = request }
serializable_resource = get_serializer(resource, options)
super(serializable_resource, options)
end
Expand Down
20 changes: 20 additions & 0 deletions lib/active_model/serializer/adapter/json_api.rb
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'

module ActiveModel
class Serializer
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions lib/active_model/serializer/adapter/json_api/pagination_links.rb
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
Loading

0 comments on commit 87c47f8

Please sign in to comment.