cheap_ams 0.10.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|