active_model_serializers 0.10.0.rc5 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +10 -0
- data/.travis.yml +0 -8
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +14 -4
- data/Gemfile +1 -2
- data/README.md +3 -3
- data/active_model_serializers.gemspec +1 -1
- data/appveyor.yml +6 -10
- data/docs/ARCHITECTURE.md +1 -1
- data/docs/README.md +1 -0
- data/docs/general/deserialization.md +19 -19
- data/docs/general/serializers.md +33 -0
- data/docs/howto/serialize_poro.md +32 -0
- data/lib/active_model/serializer.rb +2 -0
- data/lib/active_model/serializer/caching.rb +185 -3
- data/lib/active_model/serializer/field.rb +36 -2
- data/lib/active_model/serializer/lint.rb +8 -18
- data/lib/active_model/serializer/version.rb +1 -1
- data/lib/active_model_serializers.rb +0 -2
- data/lib/active_model_serializers/adapter/attributes.rb +20 -38
- data/lib/active_model_serializers/adapter/base.rb +8 -15
- data/lib/active_model_serializers/adapter/json.rb +12 -2
- data/lib/active_model_serializers/adapter/json_api.rb +33 -30
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +10 -5
- data/lib/active_model_serializers/adapter/null.rb +1 -2
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +3 -2
- data/lib/active_model_serializers/serializable_resource.rb +1 -1
- data/lib/active_model_serializers/test/schema.rb +44 -9
- data/test/action_controller/json_api/transform_test.rb +2 -1
- data/test/action_controller/serialization_test.rb +3 -1
- data/test/active_model_serializers/test/schema_test.rb +5 -3
- data/test/active_model_serializers/test/serializer_test.rb +1 -2
- data/test/adapter/json/transform_test.rb +14 -14
- data/test/adapter/json_api/has_many_test.rb +3 -2
- data/test/adapter/json_api/has_one_test.rb +3 -2
- data/test/adapter/json_api/pagination_links_test.rb +39 -21
- data/test/adapter/json_api/transform_test.rb +36 -34
- data/test/adapter/polymorphic_test.rb +111 -12
- data/test/adapter_test.rb +27 -0
- data/test/array_serializer_test.rb +10 -25
- data/test/benchmark/bm_caching.rb +17 -15
- data/test/benchmark/controllers.rb +9 -2
- data/test/benchmark/fixtures.rb +56 -4
- data/test/cache_test.rb +103 -6
- data/test/fixtures/active_record.rb +10 -0
- data/test/fixtures/poro.rb +31 -3
- data/test/serializers/associations_test.rb +43 -15
- data/test/serializers/attribute_test.rb +44 -16
- data/test/serializers/meta_test.rb +5 -7
- data/test/support/isolated_unit.rb +0 -1
- data/test/test_helper.rb +19 -21
- metadata +7 -12
- data/lib/active_model_serializers/cached_serializer.rb +0 -87
- data/lib/active_model_serializers/fragment_cache.rb +0 -118
- data/test/active_model_serializers/cached_serializer_test.rb +0 -80
- data/test/active_model_serializers/fragment_cache_test.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb97c8740c2530c085ee9186ea27ddd220367fc3
|
4
|
+
data.tar.gz: 0a768d3ab122abe2c3e7f5b6c00a8dada6872433
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5589022dd6fa160c0a03981cedf46433fb1eb19e4bac5f3489908671e9e0969c48bb7a86cf03d12cf8ce825b2efe3ed6ad1b6594019635470319c899e7964617
|
7
|
+
data.tar.gz: 989cf413780f12d47a553a7aae5cba33de1625bbc022366b7e4533810b87b151302ed6000116c03234bf2b2048a913048624728a54be87cead848076924cf6ce
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -3,7 +3,6 @@ language: ruby
|
|
3
3
|
sudo: false
|
4
4
|
|
5
5
|
rvm:
|
6
|
-
- 2.0.0
|
7
6
|
- 2.1
|
8
7
|
- 2.2.3
|
9
8
|
- 2.3.0
|
@@ -26,25 +25,18 @@ env:
|
|
26
25
|
global:
|
27
26
|
- "JRUBY_OPTS='--dev -J-Xmx1024M --debug'"
|
28
27
|
matrix:
|
29
|
-
- "RAILS_VERSION=4.0"
|
30
28
|
- "RAILS_VERSION=4.1"
|
31
29
|
- "RAILS_VERSION=4.2"
|
32
30
|
- "RAILS_VERSION=master"
|
33
31
|
|
34
32
|
matrix:
|
35
33
|
exclude:
|
36
|
-
- rvm: 2.0.0
|
37
|
-
env: RAILS_VERSION=master
|
38
34
|
- rvm: 2.1
|
39
35
|
env: RAILS_VERSION=master
|
40
36
|
- rvm: jruby-9.0.4.0
|
41
37
|
env: RAILS_VERSION=master
|
42
|
-
- rvm: jruby-9.0.4.0
|
43
|
-
env: RAILS_VERSION=4.0
|
44
38
|
- rvm: jruby-head
|
45
39
|
env: RAILS_VERSION=master
|
46
|
-
- rvm: jruby-head
|
47
|
-
env: RAILS_VERSION=4.0
|
48
40
|
allow_failures:
|
49
41
|
- rvm: ruby-head
|
50
42
|
- rvm: jruby-head
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
## 0.10.x
|
2
2
|
|
3
|
+
### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0...master)
|
4
|
+
|
5
|
+
### v0.10.0 (2016-05-17)
|
6
|
+
|
7
|
+
Breaking changes:
|
8
|
+
- [#1662](https://github.com/rails-api/active_model_serializers/pull/1662) Drop support for Rails 4.0 and Ruby 2.0.0. (@remear)
|
9
|
+
|
10
|
+
Features:
|
11
|
+
- [#1677](https://github.com/rails-api/active_model_serializers/pull/1677) Add `assert_schema`, `assert_request_schema`, `assert_request_response_schema`. (@bf4)
|
12
|
+
- [#1697](https://github.com/rails-api/active_model_serializers/pull/1697) Include actual exception message with custom exceptions;
|
13
|
+
`Test::Schema` exceptions are now `Minitest::Assertion`s. (@bf4)
|
14
|
+
- [#1699](https://github.com/rails-api/active_model_serializers/pull/1699) String/Lambda support for conditional attributes/associations (@mtsmfm)
|
15
|
+
- [#1687](https://github.com/rails-api/active_model_serializers/pull/1687) Only calculate `_cache_digest` (in `cache_key`) when `skip_digest` is false. (@bf4)
|
16
|
+
- [#1647](https://github.com/rails-api/active_model_serializers/pull/1647) Restrict usage of `serializable_hash` options
|
17
|
+
to the ActiveModel::Serialization and ActiveModel::Serializers::JSON interface. (@bf4)
|
18
|
+
|
19
|
+
Fixes:
|
20
|
+
- [#1700](https://github.com/rails-api/active_model_serializers/pull/1700) Support pagination link for Kaminari when no data is returned. (@iamnader)
|
21
|
+
- [#1726](https://github.com/rails-api/active_model_serializers/pull/1726) Adds polymorphic option to association definition which includes association type/nesting in serializer (@cgmckeever)
|
22
|
+
|
23
|
+
Misc:
|
24
|
+
- [#1673](https://github.com/rails-api/active_model_serializers/pull/1673) Adds "How to" guide on using AMS with POROs (@DrSayre)
|
25
|
+
|
3
26
|
### [v0.10.0.rc5 (2016-04-04)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc4...v0.10.0.rc5)
|
4
27
|
|
5
28
|
Breaking changes:
|
data/CONTRIBUTING.md
CHANGED
@@ -7,7 +7,7 @@ Before opening an issue, try the following:
|
|
7
7
|
See if your issue can be resolved by information in the documentation.
|
8
8
|
|
9
9
|
- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master/docs)
|
10
|
-
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0
|
10
|
+
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0)
|
11
11
|
- [Guides](docs)
|
12
12
|
- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
|
13
13
|
- [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable)
|
@@ -74,10 +74,15 @@ Run a single test
|
|
74
74
|
`$ rake test TEST=path/to/test.rb TESTOPTS="--name=test_something"`
|
75
75
|
|
76
76
|
Run tests against different Rails versions by setting the RAILS_VERSION variable
|
77
|
-
and bundling gems.
|
77
|
+
and bundling gems. (save this script somewhere executable and run from top of AMS repository)
|
78
78
|
|
79
79
|
```bash
|
80
|
-
|
80
|
+
#!/usr/bin/env bash
|
81
|
+
|
82
|
+
rcommand='puts YAML.load_file("./.travis.yml")["env"]["matrix"].join(" ").gsub("RAILS_VERSION=", "")'
|
83
|
+
versions=$(ruby -ryaml -e "$rcommand")
|
84
|
+
|
85
|
+
for version in ${versions[@]}; do
|
81
86
|
export RAILS_VERSION="$version"
|
82
87
|
rm -f Gemfile.lock
|
83
88
|
bundle check || bundle --local || bundle
|
@@ -88,7 +93,12 @@ for version in 4.0 4.1 4.2 master; do
|
|
88
93
|
else
|
89
94
|
# red in ANSI
|
90
95
|
echo -e "\033[31m **** Tests failed against Rails ${RAILS_VERSION} **** \033[0m"
|
91
|
-
|
96
|
+
read -p '[Enter] any key to continue, [q] to quit...' prompt
|
97
|
+
if [ "$prompt" = 'q' ]; then
|
98
|
+
unset RAILS_VERSION
|
99
|
+
exit 1
|
100
|
+
fi
|
101
|
+
fi
|
92
102
|
unset RAILS_VERSION
|
93
103
|
done
|
94
104
|
```
|
data/Gemfile
CHANGED
@@ -44,11 +44,10 @@ end
|
|
44
44
|
group :test do
|
45
45
|
gem 'sqlite3', platform: (@windows_platforms + [:ruby])
|
46
46
|
gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby
|
47
|
-
|
48
47
|
gem 'codeclimate-test-reporter', require: false
|
49
48
|
end
|
50
49
|
|
51
50
|
group :development, :test do
|
52
|
-
gem 'rubocop', '~> 0.
|
51
|
+
gem 'rubocop', '~> 0.39.0', require: false
|
53
52
|
gem 'yard', require: false
|
54
53
|
end
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@
|
|
28
28
|
## Documentation
|
29
29
|
|
30
30
|
- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master)
|
31
|
-
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0
|
31
|
+
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0)
|
32
32
|
- [Guides](docs)
|
33
33
|
- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
|
34
34
|
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable)
|
@@ -137,7 +137,7 @@ The model can be serialized as:
|
|
137
137
|
|
138
138
|
```ruby
|
139
139
|
options = {}
|
140
|
-
serialization = SerializableResource.new(resource, options)
|
140
|
+
serialization = ActiveModelSerializers::SerializableResource.new(resource, options)
|
141
141
|
serialization.to_json
|
142
142
|
serialization.as_json
|
143
143
|
```
|
@@ -146,7 +146,7 @@ SerializableResource delegates to the adapter, which it builds as:
|
|
146
146
|
|
147
147
|
```ruby
|
148
148
|
adapter_options = {}
|
149
|
-
adapter = Adapter.create(serializer, adapter_options)
|
149
|
+
adapter = ActiveModelSerializers::Adapter.create(serializer, adapter_options)
|
150
150
|
adapter.to_json
|
151
151
|
adapter.as_json
|
152
152
|
adapter.serializable_hash
|
@@ -60,7 +60,7 @@ Gem::Specification.new do |spec|
|
|
60
60
|
|
61
61
|
spec.post_install_message = <<-EOF
|
62
62
|
NOTE: The default key case for the JsonApi adapter has changed to dashed.
|
63
|
-
See https://github.com/rails-api/active_model_serializers/blob/master/docs/general/
|
63
|
+
See https://github.com/rails-api/active_model_serializers/blob/master/docs/general/key_transforms.md
|
64
64
|
for more information on configuring this behavior.
|
65
65
|
EOF
|
66
66
|
end
|
data/appveyor.yml
CHANGED
@@ -3,23 +3,19 @@ version: '{build}'
|
|
3
3
|
skip_tags: true
|
4
4
|
|
5
5
|
environment:
|
6
|
+
JRUBY_OPTS: "--dev -J-Xmx1024M --debug"
|
6
7
|
matrix:
|
7
|
-
- ruby_version: "
|
8
|
-
- ruby_version: "
|
9
|
-
- ruby_version: "
|
10
|
-
- ruby_version: "21-x64"
|
11
|
-
- ruby_version: "jruby-9.0.4.0"
|
8
|
+
- ruby_version: "Ruby21"
|
9
|
+
- ruby_version: "Ruby21-x64"
|
10
|
+
- ruby_version: "jruby-9.0.0.0"
|
12
11
|
|
13
12
|
cache:
|
14
13
|
- vendor/bundle
|
15
14
|
|
16
15
|
install:
|
17
|
-
- SET PATH=C
|
18
|
-
- ruby --version
|
19
|
-
- gem --version
|
16
|
+
- SET PATH=C:\%ruby_version%\bin;%PATH%
|
20
17
|
- gem install bundler
|
21
|
-
-
|
22
|
-
- bundle platform
|
18
|
+
- bundle env
|
23
19
|
- bundle install --path=vendor/bundle --retry=3 --jobs=3
|
24
20
|
|
25
21
|
test_script:
|
data/docs/ARCHITECTURE.md
CHANGED
@@ -44,7 +44,7 @@ ActiveModelSerializers.
|
|
44
44
|
|
45
45
|
If the collection serializer (ArraySerializer) cannot
|
46
46
|
identify a serializer for a resource in its collection, it raises [`NoSerializerError`](https://github.com/rails-api/active_model_serializers/issues/1191#issuecomment-142327128)
|
47
|
-
which is rescued in `
|
47
|
+
which is rescued in `ActiveModel::Serializer::Reflection#build_association` which sets
|
48
48
|
the association value directly:
|
49
49
|
|
50
50
|
```ruby
|
data/docs/README.md
CHANGED
@@ -27,6 +27,7 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10.
|
|
27
27
|
- [Using ActiveModelSerializers Outside Of Controllers](howto/outside_controller_use.md)
|
28
28
|
- [Testing ActiveModelSerializers](howto/test.md)
|
29
29
|
- [Passing Arbitrary Options](howto/passing_arbitrary_options.md)
|
30
|
+
- [How to serialize a Plain-Old Ruby Object (PORO)](howto/serialize_poro.md)
|
30
31
|
|
31
32
|
## Integrations
|
32
33
|
|
@@ -35,30 +35,30 @@ Given a JSON API document,
|
|
35
35
|
|
36
36
|
```
|
37
37
|
document = {
|
38
|
-
data
|
39
|
-
id
|
40
|
-
type
|
41
|
-
attributes
|
42
|
-
title
|
43
|
-
date
|
38
|
+
'data' => {
|
39
|
+
'id' => 1,
|
40
|
+
'type' => 'post',
|
41
|
+
'attributes' => {
|
42
|
+
'title' => 'Title 1',
|
43
|
+
'date' => '2015-12-20'
|
44
44
|
},
|
45
|
-
associations
|
46
|
-
author
|
47
|
-
data
|
48
|
-
type
|
49
|
-
id
|
45
|
+
'associations' => {
|
46
|
+
'author' => {
|
47
|
+
'data' => {
|
48
|
+
'type' => 'user',
|
49
|
+
'id' => '2'
|
50
50
|
}
|
51
51
|
},
|
52
|
-
second_author
|
53
|
-
data
|
52
|
+
'second_author' => {
|
53
|
+
'data' => nil
|
54
54
|
},
|
55
|
-
comments
|
56
|
-
data
|
57
|
-
type
|
58
|
-
id
|
55
|
+
'comments' => {
|
56
|
+
'data' => [{
|
57
|
+
'type' => 'comment',
|
58
|
+
'id' => '3'
|
59
59
|
},{
|
60
|
-
type
|
61
|
-
id
|
60
|
+
'type' => 'comment',
|
61
|
+
'id' => '4'
|
62
62
|
}]
|
63
63
|
}
|
64
64
|
}
|
data/docs/general/serializers.md
CHANGED
@@ -38,6 +38,27 @@ Serialization of the resource `title`
|
|
38
38
|
|
39
39
|
### Associations
|
40
40
|
|
41
|
+
The interface for associations is, generically:
|
42
|
+
|
43
|
+
> `association_type(association_name, options, &block)`
|
44
|
+
|
45
|
+
Where:
|
46
|
+
|
47
|
+
- `association_type` may be `has_one`, `has_many`, `belongs_to`.
|
48
|
+
- `association_name` is a method name the serializer calls.
|
49
|
+
- optional: `options` may be:
|
50
|
+
- `key:` The name used for the serialized association.
|
51
|
+
- `serializer:`
|
52
|
+
- `if:`
|
53
|
+
- `unless:`
|
54
|
+
- `virtual_value:`
|
55
|
+
- `polymorphic:` defines if polymorphic relation type should be nested in serialized association.
|
56
|
+
- optional: `&block` is a context that returns the association's attributes.
|
57
|
+
- prevents `association_name` method from being called.
|
58
|
+
- return value of block is used as the association value.
|
59
|
+
- yields the `serializer` to the block.
|
60
|
+
- `include_data false` prevents the `data` key from being rendered in the JSON API relationship.
|
61
|
+
|
41
62
|
#### ::has_one
|
42
63
|
|
43
64
|
e.g.
|
@@ -58,6 +79,18 @@ def cached_blog
|
|
58
79
|
end
|
59
80
|
```
|
60
81
|
|
82
|
+
```ruby
|
83
|
+
has_one :blog, if: :show_blog?
|
84
|
+
# you can also use a string or lambda
|
85
|
+
# has_one :blog, if: 'scope.admin?'
|
86
|
+
# has_one :blog, if: -> (serializer) { serializer.scope.admin? }
|
87
|
+
# has_one :blog, if: -> { scope.admin? }
|
88
|
+
|
89
|
+
def show_blog?
|
90
|
+
scope.admin?
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
61
94
|
#### ::has_many
|
62
95
|
|
63
96
|
e.g.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
[Back to Guides](../README.md)
|
2
|
+
|
3
|
+
# How to serialize a Plain-Old Ruby Object (PORO)
|
4
|
+
|
5
|
+
When you are first getting started with ActiveModelSerializers, it may seem only `ActiveRecord::Base` objects can be serializable, but pretty much any object can be serializable with ActiveModelSerializers. Here is an example of a PORO that is serializable:
|
6
|
+
```ruby
|
7
|
+
# my_model.rb
|
8
|
+
class MyModel
|
9
|
+
alias :read_attribute_for_serialization :send
|
10
|
+
attr_accessor :id, :name, :level
|
11
|
+
|
12
|
+
def initialize(attributes)
|
13
|
+
@id = attributes[:id]
|
14
|
+
@name = attributes[:name]
|
15
|
+
@level = attributes[:level]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.model_name
|
19
|
+
@_model_name ||= ActiveModel::Name.new(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
Fortunately, ActiveModelSerializers provides a [`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/model.rb) which you can use in production code that will make your PORO a lot cleaner. The above code now becomes:
|
25
|
+
```ruby
|
26
|
+
# my_model.rb
|
27
|
+
class MyModel < ActiveModelSerializers::Model
|
28
|
+
attr_accessor :id, :name, :level
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
The default serializer would be `MyModelSerializer`.
|
@@ -18,6 +18,8 @@ require 'active_model/serializer/type'
|
|
18
18
|
# reified when subclassed to decorate a resource.
|
19
19
|
module ActiveModel
|
20
20
|
class Serializer
|
21
|
+
# @see #serializable_hash for more details on these valid keys.
|
22
|
+
SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
|
21
23
|
extend ActiveSupport::Autoload
|
22
24
|
autoload :Adapter
|
23
25
|
autoload :Null
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module ActiveModel
|
2
2
|
class Serializer
|
3
|
+
UndefinedCacheKey = Class.new(StandardError)
|
3
4
|
module Caching
|
4
5
|
extend ActiveSupport::Concern
|
5
6
|
|
@@ -17,8 +18,9 @@ module ActiveModel
|
|
17
18
|
# force
|
18
19
|
# race_condition_ttl
|
19
20
|
# Passed to ::_cache as
|
20
|
-
# serializer.
|
21
|
-
|
21
|
+
# serializer.cache_store.fetch(cache_key, @klass._cache_options)
|
22
|
+
# Passed as second argument to serializer.cache_store.fetch(cache_key, self.class._cache_options)
|
23
|
+
serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
@@ -41,7 +43,12 @@ module ActiveModel
|
|
41
43
|
def inherited(base)
|
42
44
|
super
|
43
45
|
caller_line = caller[1]
|
44
|
-
base.
|
46
|
+
base._cache_digest_file_path = caller_line
|
47
|
+
end
|
48
|
+
|
49
|
+
def _cache_digest
|
50
|
+
return @_cache_digest if defined?(@_cache_digest)
|
51
|
+
@_cache_digest = digest_caller_file(_cache_digest_file_path)
|
45
52
|
end
|
46
53
|
|
47
54
|
# Hashes contents of file for +_cache_digest+
|
@@ -62,6 +69,14 @@ module ActiveModel
|
|
62
69
|
_cache_options && _cache_options[:skip_digest]
|
63
70
|
end
|
64
71
|
|
72
|
+
def cached_attributes
|
73
|
+
_cache_only ? _cache_only : _attributes - _cache_except
|
74
|
+
end
|
75
|
+
|
76
|
+
def non_cached_attributes
|
77
|
+
_attributes - cached_attributes
|
78
|
+
end
|
79
|
+
|
65
80
|
# @api private
|
66
81
|
# Used by FragmentCache on the CachedSerializer
|
67
82
|
# to call attribute methods on the fragmented cached serializer.
|
@@ -145,6 +160,173 @@ module ActiveModel
|
|
145
160
|
perform_caching? && cache_store &&
|
146
161
|
(_cache_only && !_cache_except || !_cache_only && _cache_except)
|
147
162
|
end
|
163
|
+
|
164
|
+
# Read cache from cache_store
|
165
|
+
# @return [Hash]
|
166
|
+
def cache_read_multi(collection_serializer, adapter_instance, include_tree)
|
167
|
+
return {} if ActiveModelSerializers.config.cache_store.blank?
|
168
|
+
|
169
|
+
keys = object_cache_keys(collection_serializer, adapter_instance, include_tree)
|
170
|
+
|
171
|
+
return {} if keys.blank?
|
172
|
+
|
173
|
+
ActiveModelSerializers.config.cache_store.read_multi(*keys)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Find all cache_key for the collection_serializer
|
177
|
+
# @param serializers [ActiveModel::Serializer::CollectionSerializer]
|
178
|
+
# @param adapter_instance [ActiveModelSerializers::Adapter::Base]
|
179
|
+
# @param include_tree [ActiveModel::Serializer::IncludeTree]
|
180
|
+
# @return [Array] all cache_key of collection_serializer
|
181
|
+
def object_cache_keys(collection_serializer, adapter_instance, include_tree)
|
182
|
+
cache_keys = []
|
183
|
+
|
184
|
+
collection_serializer.each do |serializer|
|
185
|
+
cache_keys << object_cache_key(serializer, adapter_instance)
|
186
|
+
|
187
|
+
serializer.associations(include_tree).each do |association|
|
188
|
+
if association.serializer.respond_to?(:each)
|
189
|
+
association.serializer.each do |sub_serializer|
|
190
|
+
cache_keys << object_cache_key(sub_serializer, adapter_instance)
|
191
|
+
end
|
192
|
+
else
|
193
|
+
cache_keys << object_cache_key(association.serializer, adapter_instance)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
cache_keys.compact.uniq
|
199
|
+
end
|
200
|
+
|
201
|
+
# @return [String, nil] the cache_key of the serializer or nil
|
202
|
+
def object_cache_key(serializer, adapter_instance)
|
203
|
+
return unless serializer.present? && serializer.object.present?
|
204
|
+
|
205
|
+
serializer.class.cache_enabled? ? serializer.cache_key(adapter_instance) : nil
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Get attributes from @cached_attributes
|
210
|
+
# @return [Hash] cached attributes
|
211
|
+
# def cached_attributes(fields, adapter_instance)
|
212
|
+
def cached_fields(fields, adapter_instance)
|
213
|
+
cache_check(adapter_instance) do
|
214
|
+
attributes(fields)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def cache_check(adapter_instance)
|
219
|
+
if self.class.cache_enabled?
|
220
|
+
self.class.cache_store.fetch(cache_key(adapter_instance), self.class._cache_options) do
|
221
|
+
yield
|
222
|
+
end
|
223
|
+
elsif self.class.fragment_cache_enabled?
|
224
|
+
fetch_fragment_cache(adapter_instance)
|
225
|
+
else
|
226
|
+
yield
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# 1. Create a CachedSerializer and NonCachedSerializer from the serializer class
|
231
|
+
# 2. Serialize the above two with the given adapter
|
232
|
+
# 3. Pass their serializations to the adapter +::fragment_cache+
|
233
|
+
#
|
234
|
+
# It will split the serializer into two, one that will be cached and one that will not
|
235
|
+
#
|
236
|
+
# Given a resource name
|
237
|
+
# 1. Dynamically creates a CachedSerializer and NonCachedSerializer
|
238
|
+
# for a given class 'name'
|
239
|
+
# 2. Call
|
240
|
+
# CachedSerializer.cache(serializer._cache_options)
|
241
|
+
# CachedSerializer.fragmented(serializer)
|
242
|
+
# NonCachedSerializer.cache(serializer._cache_options)
|
243
|
+
# 3. Build a hash keyed to the +cached+ and +non_cached+ serializers
|
244
|
+
# 4. Call +cached_attributes+ on the serializer class and the above hash
|
245
|
+
# 5. Return the hash
|
246
|
+
#
|
247
|
+
# @example
|
248
|
+
# When +name+ is <tt>User::Admin</tt>
|
249
|
+
# creates the Serializer classes (if they don't exist).
|
250
|
+
# CachedUser_AdminSerializer
|
251
|
+
# NonCachedUser_AdminSerializer
|
252
|
+
#
|
253
|
+
# Given a hash of its cached and non-cached serializers
|
254
|
+
# 1. Determine cached attributes from serializer class options
|
255
|
+
# 2. Add cached attributes to cached Serializer
|
256
|
+
# 3. Add non-cached attributes to non-cached Serializer
|
257
|
+
def fetch_fragment_cache(adapter_instance)
|
258
|
+
serializer_class_name = self.class.name.gsub('::'.freeze, '_'.freeze)
|
259
|
+
self.class._cache_options ||= {}
|
260
|
+
self.class._cache_options[:key] = self.class._cache_key if self.class._cache_key
|
261
|
+
|
262
|
+
cached_serializer = _get_or_create_fragment_cached_serializer(serializer_class_name)
|
263
|
+
cached_hash = ActiveModelSerializers::SerializableResource.new(
|
264
|
+
object,
|
265
|
+
serializer: cached_serializer,
|
266
|
+
adapter: adapter_instance.class
|
267
|
+
).serializable_hash
|
268
|
+
|
269
|
+
non_cached_serializer = _get_or_create_fragment_non_cached_serializer(serializer_class_name)
|
270
|
+
non_cached_hash = ActiveModelSerializers::SerializableResource.new(
|
271
|
+
object,
|
272
|
+
serializer: non_cached_serializer,
|
273
|
+
adapter: adapter_instance.class
|
274
|
+
).serializable_hash
|
275
|
+
|
276
|
+
# Merge both results
|
277
|
+
adapter_instance.fragment_cache(cached_hash, non_cached_hash)
|
278
|
+
end
|
279
|
+
|
280
|
+
def _get_or_create_fragment_cached_serializer(serializer_class_name)
|
281
|
+
cached_serializer = _get_or_create_fragment_serializer "Cached#{serializer_class_name}"
|
282
|
+
cached_serializer.cache(self.class._cache_options)
|
283
|
+
cached_serializer.type(self.class._type)
|
284
|
+
cached_serializer.fragmented(self)
|
285
|
+
self.class.cached_attributes.each do |attribute|
|
286
|
+
options = self.class._attributes_keys[attribute] || {}
|
287
|
+
cached_serializer.attribute(attribute, options)
|
288
|
+
end
|
289
|
+
cached_serializer
|
290
|
+
end
|
291
|
+
|
292
|
+
def _get_or_create_fragment_non_cached_serializer(serializer_class_name)
|
293
|
+
non_cached_serializer = _get_or_create_fragment_serializer "NonCached#{serializer_class_name}"
|
294
|
+
non_cached_serializer.type(self.class._type)
|
295
|
+
non_cached_serializer.fragmented(self)
|
296
|
+
self.class.non_cached_attributes.each do |attribute|
|
297
|
+
options = self.class._attributes_keys[attribute] || {}
|
298
|
+
non_cached_serializer.attribute(attribute, options)
|
299
|
+
end
|
300
|
+
non_cached_serializer
|
301
|
+
end
|
302
|
+
|
303
|
+
def _get_or_create_fragment_serializer(name)
|
304
|
+
return Object.const_get(name) if Object.const_defined?(name)
|
305
|
+
Object.const_set(name, Class.new(ActiveModel::Serializer))
|
306
|
+
end
|
307
|
+
|
308
|
+
def cache_key(adapter_instance)
|
309
|
+
return @cache_key if defined?(@cache_key)
|
310
|
+
|
311
|
+
parts = []
|
312
|
+
parts << object_cache_key
|
313
|
+
parts << adapter_instance.cached_name
|
314
|
+
parts << self.class._cache_digest unless self.class._skip_digest?
|
315
|
+
@cache_key = parts.join('/')
|
316
|
+
end
|
317
|
+
|
318
|
+
# Use object's cache_key if available, else derive a key from the object
|
319
|
+
# Pass the `key` option to the `cache` declaration or override this method to customize the cache key
|
320
|
+
def object_cache_key
|
321
|
+
if object.respond_to?(:cache_key)
|
322
|
+
object.cache_key
|
323
|
+
elsif (serializer_cache_key = (self.class._cache_key || self.class._cache_options[:key]))
|
324
|
+
object_time_safe = object.updated_at
|
325
|
+
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
|
326
|
+
"#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
|
327
|
+
else
|
328
|
+
fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{self.class}.cache'"
|
329
|
+
end
|
148
330
|
end
|
149
331
|
end
|
150
332
|
end
|