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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.travis.yml +0 -8
  4. data/CHANGELOG.md +23 -0
  5. data/CONTRIBUTING.md +14 -4
  6. data/Gemfile +1 -2
  7. data/README.md +3 -3
  8. data/active_model_serializers.gemspec +1 -1
  9. data/appveyor.yml +6 -10
  10. data/docs/ARCHITECTURE.md +1 -1
  11. data/docs/README.md +1 -0
  12. data/docs/general/deserialization.md +19 -19
  13. data/docs/general/serializers.md +33 -0
  14. data/docs/howto/serialize_poro.md +32 -0
  15. data/lib/active_model/serializer.rb +2 -0
  16. data/lib/active_model/serializer/caching.rb +185 -3
  17. data/lib/active_model/serializer/field.rb +36 -2
  18. data/lib/active_model/serializer/lint.rb +8 -18
  19. data/lib/active_model/serializer/version.rb +1 -1
  20. data/lib/active_model_serializers.rb +0 -2
  21. data/lib/active_model_serializers/adapter/attributes.rb +20 -38
  22. data/lib/active_model_serializers/adapter/base.rb +8 -15
  23. data/lib/active_model_serializers/adapter/json.rb +12 -2
  24. data/lib/active_model_serializers/adapter/json_api.rb +33 -30
  25. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +10 -5
  26. data/lib/active_model_serializers/adapter/null.rb +1 -2
  27. data/lib/active_model_serializers/register_jsonapi_renderer.rb +3 -2
  28. data/lib/active_model_serializers/serializable_resource.rb +1 -1
  29. data/lib/active_model_serializers/test/schema.rb +44 -9
  30. data/test/action_controller/json_api/transform_test.rb +2 -1
  31. data/test/action_controller/serialization_test.rb +3 -1
  32. data/test/active_model_serializers/test/schema_test.rb +5 -3
  33. data/test/active_model_serializers/test/serializer_test.rb +1 -2
  34. data/test/adapter/json/transform_test.rb +14 -14
  35. data/test/adapter/json_api/has_many_test.rb +3 -2
  36. data/test/adapter/json_api/has_one_test.rb +3 -2
  37. data/test/adapter/json_api/pagination_links_test.rb +39 -21
  38. data/test/adapter/json_api/transform_test.rb +36 -34
  39. data/test/adapter/polymorphic_test.rb +111 -12
  40. data/test/adapter_test.rb +27 -0
  41. data/test/array_serializer_test.rb +10 -25
  42. data/test/benchmark/bm_caching.rb +17 -15
  43. data/test/benchmark/controllers.rb +9 -2
  44. data/test/benchmark/fixtures.rb +56 -4
  45. data/test/cache_test.rb +103 -6
  46. data/test/fixtures/active_record.rb +10 -0
  47. data/test/fixtures/poro.rb +31 -3
  48. data/test/serializers/associations_test.rb +43 -15
  49. data/test/serializers/attribute_test.rb +44 -16
  50. data/test/serializers/meta_test.rb +5 -7
  51. data/test/support/isolated_unit.rb +0 -1
  52. data/test/test_helper.rb +19 -21
  53. metadata +7 -12
  54. data/lib/active_model_serializers/cached_serializer.rb +0 -87
  55. data/lib/active_model_serializers/fragment_cache.rb +0 -118
  56. data/test/active_model_serializers/cached_serializer_test.rb +0 -80
  57. 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: 79d558ff503a5d3d80c7e28d07fbad6858c87416
4
- data.tar.gz: e1345a0b533ac95996f62e873366907377f8e175
3
+ metadata.gz: eb97c8740c2530c085ee9186ea27ddd220367fc3
4
+ data.tar.gz: 0a768d3ab122abe2c3e7f5b6c00a8dada6872433
5
5
  SHA512:
6
- metadata.gz: ded6c2f6e8a7bbe81543a4939ae02c255908697a3894556bc81b5c46cb21b81cf771370fc60a902d393391b0b3addd9a5018cf142a04ecf3752c2aa1dcab2292
7
- data.tar.gz: 285de3be1603f3ad013da4813ff3a95528e99bf3b2b4f511e780cefb6babd8d9555b113a3eaca762400e64e941d2fc699de02a73745ff56d7365b9dc6c41957a
6
+ metadata.gz: 5589022dd6fa160c0a03981cedf46433fb1eb19e4bac5f3489908671e9e0969c48bb7a86cf03d12cf8ce825b2efe3ed6ad1b6594019635470319c899e7964617
7
+ data.tar.gz: 989cf413780f12d47a553a7aae5cba33de1625bbc022366b7e4533810b87b151302ed6000116c03234bf2b2048a913048624728a54be87cead848076924cf6ce
data/.gitignore CHANGED
@@ -23,3 +23,13 @@ tmp
23
23
  .ruby-gemset
24
24
  vendor/bundle
25
25
  tags
26
+
27
+ # silly macs
28
+ .DS_Store
29
+ .DS_Store?
30
+ ._*
31
+ .Spotlight-V100
32
+ .Trashes
33
+ Icon?
34
+ ehthumbs.db
35
+ Thumbs.db
@@ -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
@@ -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:
@@ -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.rc5)
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
- for version in 4.0 4.1 4.2 master; do
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
- fi
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.36', require: false
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.rc5)
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/key_transform.md
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
@@ -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: "200"
8
- - ruby_version: "200-x64"
9
- - ruby_version: "21"
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:\Ruby%ruby_version%\bin;%PATH%
18
- - ruby --version
19
- - gem --version
16
+ - SET PATH=C:\%ruby_version%\bin;%PATH%
20
17
  - gem install bundler
21
- - bundler --version
22
- - bundle platform
18
+ - bundle env
23
19
  - bundle install --path=vendor/bundle --retry=3 --jobs=3
24
20
 
25
21
  test_script:
@@ -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 `AcitveModel::Serializer::Reflection#build_association` which sets
47
+ which is rescued in `ActiveModel::Serializer::Reflection#build_association` which sets
48
48
  the association value directly:
49
49
 
50
50
  ```ruby
@@ -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: 1,
40
- type: 'post',
41
- attributes: {
42
- title: 'Title 1',
43
- date: '2015-12-20'
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: 'user',
49
- id: 2
45
+ 'associations' => {
46
+ 'author' => {
47
+ 'data' => {
48
+ 'type' => 'user',
49
+ 'id' => '2'
50
50
  }
51
51
  },
52
- second_author: {
53
- data: nil
52
+ 'second_author' => {
53
+ 'data' => nil
54
54
  },
55
- comments: {
56
- data: [{
57
- type: 'comment',
58
- id: 3
55
+ 'comments' => {
56
+ 'data' => [{
57
+ 'type' => 'comment',
58
+ 'id' => '3'
59
59
  },{
60
- type: 'comment',
61
- id: 4
60
+ 'type' => 'comment',
61
+ 'id' => '4'
62
62
  }]
63
63
  }
64
64
  }
@@ -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._cache.fetch(cache_key, @klass._cache_options)
21
- serializer.class_attribute :_cache_digest # @api private : Generated
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._cache_digest = digest_caller_file(caller_line)
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