cheap_ams 0.10.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +26 -0
- data/CHANGELOG.md +13 -0
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +35 -0
- data/LICENSE.txt +22 -0
- data/README.md +348 -0
- data/Rakefile +12 -0
- data/appveyor.yml +25 -0
- data/cheap_ams.gemspec +49 -0
- data/docs/README.md +27 -0
- data/docs/general/adapters.md +51 -0
- data/docs/general/getting_started.md +73 -0
- data/docs/howto/add_pagination_links.md +112 -0
- data/docs/howto/add_root_key.md +51 -0
- data/lib/action_controller/serialization.rb +62 -0
- data/lib/active_model/serializable_resource.rb +84 -0
- data/lib/active_model/serializer/adapter/flatten_json.rb +19 -0
- data/lib/active_model/serializer/adapter/fragment_cache.rb +82 -0
- data/lib/active_model/serializer/adapter/json/fragment_cache.rb +16 -0
- data/lib/active_model/serializer/adapter/json.rb +53 -0
- data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +24 -0
- data/lib/active_model/serializer/adapter/json_api/pagination_links.rb +58 -0
- data/lib/active_model/serializer/adapter/json_api.rb +183 -0
- data/lib/active_model/serializer/adapter/null.rb +11 -0
- data/lib/active_model/serializer/adapter.rb +98 -0
- data/lib/active_model/serializer/array_serializer.rb +35 -0
- data/lib/active_model/serializer/association.rb +21 -0
- data/lib/active_model/serializer/associations.rb +97 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
- data/lib/active_model/serializer/collection_reflection.rb +7 -0
- data/lib/active_model/serializer/configuration.rb +14 -0
- data/lib/active_model/serializer/fieldset.rb +42 -0
- data/lib/active_model/serializer/has_many_reflection.rb +10 -0
- data/lib/active_model/serializer/has_one_reflection.rb +10 -0
- data/lib/active_model/serializer/lint.rb +131 -0
- data/lib/active_model/serializer/railtie.rb +9 -0
- data/lib/active_model/serializer/reflection.rb +74 -0
- data/lib/active_model/serializer/singular_reflection.rb +7 -0
- data/lib/active_model/serializer/version.rb +5 -0
- data/lib/active_model/serializer.rb +205 -0
- data/lib/active_model_serializers.rb +29 -0
- data/lib/generators/serializer/USAGE +6 -0
- data/lib/generators/serializer/resource_override.rb +12 -0
- data/lib/generators/serializer/serializer_generator.rb +37 -0
- data/lib/generators/serializer/templates/serializer.rb +8 -0
- data/test/action_controller/adapter_selector_test.rb +53 -0
- data/test/action_controller/explicit_serializer_test.rb +134 -0
- data/test/action_controller/json_api/linked_test.rb +180 -0
- data/test/action_controller/json_api/pagination_test.rb +116 -0
- data/test/action_controller/serialization_scope_name_test.rb +67 -0
- data/test/action_controller/serialization_test.rb +426 -0
- data/test/adapter/fragment_cache_test.rb +37 -0
- data/test/adapter/json/belongs_to_test.rb +47 -0
- data/test/adapter/json/collection_test.rb +82 -0
- data/test/adapter/json/has_many_test.rb +47 -0
- data/test/adapter/json_api/belongs_to_test.rb +157 -0
- data/test/adapter/json_api/collection_test.rb +96 -0
- data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
- data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
- data/test/adapter/json_api/has_many_test.rb +145 -0
- data/test/adapter/json_api/has_one_test.rb +81 -0
- data/test/adapter/json_api/json_api_test.rb +38 -0
- data/test/adapter/json_api/linked_test.rb +283 -0
- data/test/adapter/json_api/pagination_links_test.rb +115 -0
- data/test/adapter/json_api/resource_type_config_test.rb +59 -0
- data/test/adapter/json_test.rb +47 -0
- data/test/adapter/null_test.rb +25 -0
- data/test/adapter_test.rb +52 -0
- data/test/array_serializer_test.rb +97 -0
- data/test/capture_warnings.rb +57 -0
- data/test/fixtures/active_record.rb +57 -0
- data/test/fixtures/poro.rb +266 -0
- data/test/generators/scaffold_controller_generator_test.rb +24 -0
- data/test/generators/serializer_generator_test.rb +56 -0
- data/test/lint_test.rb +44 -0
- data/test/poro_test.rb +9 -0
- data/test/serializable_resource_test.rb +27 -0
- data/test/serializers/adapter_for_test.rb +50 -0
- data/test/serializers/association_macros_test.rb +36 -0
- data/test/serializers/associations_test.rb +150 -0
- data/test/serializers/attribute_test.rb +62 -0
- data/test/serializers/attributes_test.rb +63 -0
- data/test/serializers/cache_test.rb +164 -0
- data/test/serializers/configuration_test.rb +15 -0
- data/test/serializers/fieldset_test.rb +26 -0
- data/test/serializers/meta_test.rb +121 -0
- data/test/serializers/options_test.rb +21 -0
- data/test/serializers/root_test.rb +23 -0
- data/test/serializers/serializer_for_test.rb +65 -0
- data/test/serializers/urls_test.rb +26 -0
- data/test/support/rails_app.rb +21 -0
- data/test/support/stream_capture.rb +49 -0
- data/test/support/test_case.rb +5 -0
- data/test/test_helper.rb +41 -0
- metadata +287 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
### ActiveModel::Serializer is already included on Rails >= 5
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem 'active_model_serializers'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
## Creating a Serializer
|
20
|
+
|
21
|
+
The easiest way to create a new serializer is to generate a new resource, which
|
22
|
+
will generate a serializer at the same time:
|
23
|
+
|
24
|
+
```
|
25
|
+
$ rails g resource post title:string body:string
|
26
|
+
```
|
27
|
+
|
28
|
+
This will generate a serializer in `app/serializers/post_serializer.rb` for
|
29
|
+
your new model. You can also generate a serializer for an existing model with
|
30
|
+
the serializer generator:
|
31
|
+
|
32
|
+
```
|
33
|
+
$ rails g serializer post
|
34
|
+
```
|
35
|
+
|
36
|
+
The generated seralizer will contain basic `attributes` and
|
37
|
+
`has_many`/`has_one`/`belongs_to` declarations, based on the model. For example:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class PostSerializer < ActiveModel::Serializer
|
41
|
+
attributes :title, :body
|
42
|
+
|
43
|
+
has_many :comments
|
44
|
+
has_one :author
|
45
|
+
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
and
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class CommentSerializer < ActiveModel::Serializer
|
53
|
+
attributes :name, :body
|
54
|
+
|
55
|
+
belongs_to :post_id
|
56
|
+
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
## Rails Integration
|
61
|
+
|
62
|
+
AMS will automatically integrate with you Rails app, you won't need to update your controller, this is a example of how it will look like:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class PostsController < ApplicationController
|
66
|
+
|
67
|
+
def show
|
68
|
+
@post = Post.find(params[:id])
|
69
|
+
render json: @post
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
```
|
@@ -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.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# How to add root key
|
2
|
+
|
3
|
+
Add the root key to your API is quite simple with AMS. The **Adapter** is what determines the format of your JSON response. The default adapter is the ```FlattenJSON``` which doesn't have the root key, so your response is something similar to:
|
4
|
+
|
5
|
+
```json
|
6
|
+
{
|
7
|
+
"id": 1,
|
8
|
+
"title": "Awesome Post Tile",
|
9
|
+
"content": "Post content"
|
10
|
+
}
|
11
|
+
```
|
12
|
+
|
13
|
+
In order to add the root key you need to use the ```JSON``` Adapter, you can change this in an initializer:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
ActiveModel::Serializer.config.adapter = :json
|
17
|
+
```
|
18
|
+
|
19
|
+
You can also specify a class as adapter, as long as it complies with the AMS adapters interface.
|
20
|
+
It will add the root key to all your serialized endpoints.
|
21
|
+
|
22
|
+
ex:
|
23
|
+
|
24
|
+
```json
|
25
|
+
{
|
26
|
+
"post": {
|
27
|
+
"id": 1,
|
28
|
+
"title": "Awesome Post Tile",
|
29
|
+
"content": "Post content"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
```
|
33
|
+
|
34
|
+
or if it returns a collection:
|
35
|
+
|
36
|
+
```json
|
37
|
+
{
|
38
|
+
"posts": [
|
39
|
+
{
|
40
|
+
"id": 1,
|
41
|
+
"title": "Awesome Post Tile",
|
42
|
+
"content": "Post content"
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"id": 2,
|
46
|
+
"title": "Another Post Tile",
|
47
|
+
"content": "Another post content"
|
48
|
+
}
|
49
|
+
]
|
50
|
+
}
|
51
|
+
```
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
module Serialization
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
include ActionController::Renderers
|
8
|
+
|
9
|
+
# Deprecated
|
10
|
+
ADAPTER_OPTION_KEYS = ActiveModel::SerializableResource::ADAPTER_OPTION_KEYS
|
11
|
+
|
12
|
+
included do
|
13
|
+
class_attribute :_serialization_scope
|
14
|
+
self._serialization_scope = :current_user
|
15
|
+
end
|
16
|
+
|
17
|
+
def serialization_scope
|
18
|
+
send(_serialization_scope) if _serialization_scope &&
|
19
|
+
respond_to?(_serialization_scope, true)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_serializer(resource, options = {})
|
23
|
+
if ! use_adapter?
|
24
|
+
warn "ActionController::Serialization#use_adapter? has been removed. "\
|
25
|
+
"Please pass 'adapter: false' or see ActiveSupport::SerializableResource#serialize"
|
26
|
+
options[:adapter] = false
|
27
|
+
end
|
28
|
+
ActiveModel::SerializableResource.serialize(resource, options) do |serializable_resource|
|
29
|
+
if serializable_resource.serializer?
|
30
|
+
serializable_resource.serialization_scope ||= serialization_scope
|
31
|
+
serializable_resource.serialization_scope_name = _serialization_scope
|
32
|
+
begin
|
33
|
+
serializable_resource.adapter
|
34
|
+
rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
|
35
|
+
resource
|
36
|
+
end
|
37
|
+
else
|
38
|
+
resource
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Deprecated
|
44
|
+
def use_adapter?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
|
49
|
+
define_method renderer_method do |resource, options|
|
50
|
+
options.fetch(:context) { options[:context] = request }
|
51
|
+
serializable_resource = get_serializer(resource, options)
|
52
|
+
super(serializable_resource, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
def serialization_scope(scope)
|
58
|
+
self._serialization_scope = scope
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'set'
|
2
|
+
module ActiveModel
|
3
|
+
class SerializableResource
|
4
|
+
|
5
|
+
ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter])
|
6
|
+
|
7
|
+
def initialize(resource, options = {})
|
8
|
+
@resource = resource
|
9
|
+
@adapter_opts, @serializer_opts =
|
10
|
+
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
|
11
|
+
end
|
12
|
+
|
13
|
+
delegate :serializable_hash, :as_json, :to_json, to: :adapter
|
14
|
+
|
15
|
+
# Primary interface to building a serializer (with adapter)
|
16
|
+
# If no block is given,
|
17
|
+
# returns the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
|
18
|
+
# Otherwise, yields the serializable_resource and
|
19
|
+
# returns the contents of the block
|
20
|
+
def self.serialize(resource, options = {})
|
21
|
+
serializable_resource = SerializableResource.new(resource, options)
|
22
|
+
if block_given?
|
23
|
+
yield serializable_resource
|
24
|
+
else
|
25
|
+
serializable_resource
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialization_scope=(scope)
|
30
|
+
serializer_opts[:scope] = scope
|
31
|
+
end
|
32
|
+
|
33
|
+
def serialization_scope
|
34
|
+
serializer_opts[:scope]
|
35
|
+
end
|
36
|
+
|
37
|
+
def serialization_scope_name=(scope_name)
|
38
|
+
serializer_opts[:scope_name] = scope_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def adapter
|
42
|
+
@adapter ||= ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)
|
43
|
+
end
|
44
|
+
alias_method :adapter_instance, :adapter
|
45
|
+
|
46
|
+
def serializer_instance
|
47
|
+
@serializer_instance ||= serializer.new(resource, serializer_opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get serializer either explicitly :serializer or implicitly from resource
|
51
|
+
# Remove :serializer key from serializer_opts
|
52
|
+
# Replace :serializer key with :each_serializer if present
|
53
|
+
def serializer
|
54
|
+
@serializer ||=
|
55
|
+
begin
|
56
|
+
@serializer = serializer_opts.delete(:serializer)
|
57
|
+
@serializer ||= ActiveModel::Serializer.serializer_for(resource)
|
58
|
+
|
59
|
+
if serializer_opts.key?(:each_serializer)
|
60
|
+
serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
|
61
|
+
end
|
62
|
+
@serializer
|
63
|
+
end
|
64
|
+
end
|
65
|
+
alias_method :serializer_class, :serializer
|
66
|
+
|
67
|
+
# True when no explicit adapter given, or explicit appear is truthy (non-nil)
|
68
|
+
# False when explicit adapter is falsy (nil or false)
|
69
|
+
def use_adapter?
|
70
|
+
!(adapter_opts.key?(:adapter) && !adapter_opts[:adapter])
|
71
|
+
end
|
72
|
+
|
73
|
+
def serializer?
|
74
|
+
use_adapter? && !!(serializer)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
ActiveModelSerializers.silence_warnings do
|
80
|
+
attr_reader :resource, :adapter_opts, :serializer_opts
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Adapter
|
4
|
+
class FlattenJson < Json
|
5
|
+
def serializable_hash(options = {})
|
6
|
+
super
|
7
|
+
@result
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# no-op: FlattenJson adapter does not include meta data, because it does not support root.
|
13
|
+
def include_meta(json)
|
14
|
+
json
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Adapter
|
4
|
+
class FragmentCache
|
5
|
+
|
6
|
+
attr_reader :serializer
|
7
|
+
|
8
|
+
def initialize(adapter, serializer, options)
|
9
|
+
@options = options
|
10
|
+
@adapter = adapter
|
11
|
+
@serializer = serializer
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch
|
15
|
+
klass = serializer.class
|
16
|
+
# It will split the serializer into two, one that will be cached and other wont
|
17
|
+
serializers = fragment_serializer(serializer.object.class.name, klass)
|
18
|
+
|
19
|
+
# Instanciate both serializers
|
20
|
+
cached_serializer = serializers[:cached].constantize.new(serializer.object)
|
21
|
+
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
|
22
|
+
|
23
|
+
cached_adapter = @adapter.class.new(cached_serializer, @options)
|
24
|
+
non_cached_adapter = @adapter.class.new(non_cached_serializer, @options)
|
25
|
+
|
26
|
+
# Get serializable hash from both
|
27
|
+
cached_hash = cached_adapter.serializable_hash
|
28
|
+
non_cached_hash = non_cached_adapter.serializable_hash
|
29
|
+
|
30
|
+
# Merge both results
|
31
|
+
@adapter.fragment_cache(cached_hash, non_cached_hash)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def cached_attributes(klass, serializers)
|
37
|
+
attributes = serializer.class._attributes
|
38
|
+
cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject {|attr| klass._cache_except.include?(attr) }
|
39
|
+
non_cached_attributes = attributes - cached_attributes
|
40
|
+
|
41
|
+
cached_attributes.each do |attribute|
|
42
|
+
options = serializer.class._attributes_keys[attribute]
|
43
|
+
options ||= {}
|
44
|
+
# Add cached attributes to cached Serializer
|
45
|
+
serializers[:cached].constantize.attribute(attribute, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
non_cached_attributes.each do |attribute|
|
49
|
+
options = serializer.class._attributes_keys[attribute]
|
50
|
+
options ||= {}
|
51
|
+
# Add non-cached attributes to non-cached Serializer
|
52
|
+
serializers[:non_cached].constantize.attribute(attribute, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fragment_serializer(name, klass)
|
57
|
+
cached = "#{to_valid_const_name(name)}CachedSerializer"
|
58
|
+
non_cached = "#{to_valid_const_name(name)}NonCachedSerializer"
|
59
|
+
|
60
|
+
Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached)
|
61
|
+
Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached)
|
62
|
+
|
63
|
+
klass._cache_options ||= {}
|
64
|
+
klass._cache_options[:key] = klass._cache_key if klass._cache_key
|
65
|
+
|
66
|
+
cached.constantize.cache(klass._cache_options)
|
67
|
+
|
68
|
+
cached.constantize.fragmented(serializer)
|
69
|
+
non_cached.constantize.fragmented(serializer)
|
70
|
+
|
71
|
+
serializers = {cached: cached, non_cached: non_cached}
|
72
|
+
cached_attributes(klass, serializers)
|
73
|
+
serializers
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_valid_const_name(name)
|
77
|
+
name.gsub('::', '_')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'active_model/serializer/adapter/fragment_cache'
|
2
|
+
module ActiveModel
|
3
|
+
class Serializer
|
4
|
+
class Adapter
|
5
|
+
class Json < Adapter
|
6
|
+
class FragmentCache
|
7
|
+
|
8
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
9
|
+
non_cached_hash.merge cached_hash
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'active_model/serializer/adapter/json/fragment_cache'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
class Adapter
|
6
|
+
class Json < Adapter
|
7
|
+
def serializable_hash(options = nil)
|
8
|
+
options ||= {}
|
9
|
+
if serializer.respond_to?(:each)
|
10
|
+
@result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
|
11
|
+
else
|
12
|
+
@hash = {}
|
13
|
+
|
14
|
+
@core = cache_check(serializer) do
|
15
|
+
serializer.attributes(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
serializer.associations.each do |association|
|
19
|
+
serializer = association.serializer
|
20
|
+
opts = association.options
|
21
|
+
|
22
|
+
if serializer.respond_to?(:each)
|
23
|
+
array_serializer = serializer
|
24
|
+
@hash[association.key] = array_serializer.map do |item|
|
25
|
+
cache_check(item) do
|
26
|
+
item.attributes(opts)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
else
|
30
|
+
@hash[association.key] =
|
31
|
+
if serializer && serializer.object
|
32
|
+
cache_check(serializer) do
|
33
|
+
serializer.attributes(options)
|
34
|
+
end
|
35
|
+
elsif opts[:virtual_value]
|
36
|
+
opts[:virtual_value]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@result = @core.merge @hash
|
41
|
+
end
|
42
|
+
|
43
|
+
{ root => @result }
|
44
|
+
end
|
45
|
+
|
46
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
47
|
+
Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_model/serializer/adapter/fragment_cache'
|
2
|
+
module ActiveModel
|
3
|
+
class Serializer
|
4
|
+
class Adapter
|
5
|
+
class JsonApi < Adapter
|
6
|
+
class FragmentCache
|
7
|
+
|
8
|
+
def fragment_cache(root, cached_hash, non_cached_hash)
|
9
|
+
hash = {}
|
10
|
+
core_cached = cached_hash.first
|
11
|
+
core_non_cached = non_cached_hash.first
|
12
|
+
no_root_cache = cached_hash.delete_if {|key, value| key == core_cached[0] }
|
13
|
+
no_root_non_cache = non_cached_hash.delete_if {|key, value| key == core_non_cached[0] }
|
14
|
+
cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
|
15
|
+
hash = (root) ? { root => cached_resource } : cached_resource
|
16
|
+
|
17
|
+
hash.deep_merge no_root_non_cache.deep_merge no_root_cache
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -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
|