active_model_serializers 0.10.0.rc5 → 0.10.0

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