active_model_serializers 0.10.0 → 0.10.13

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 (215) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +239 -2
  3. data/README.md +171 -34
  4. data/lib/action_controller/serialization.rb +23 -3
  5. data/lib/active_model/serializable_resource.rb +2 -0
  6. data/lib/active_model/serializer/adapter/attributes.rb +2 -0
  7. data/lib/active_model/serializer/adapter/base.rb +4 -0
  8. data/lib/active_model/serializer/adapter/json.rb +2 -0
  9. data/lib/active_model/serializer/adapter/json_api.rb +2 -0
  10. data/lib/active_model/serializer/adapter/null.rb +2 -0
  11. data/lib/active_model/serializer/adapter.rb +2 -0
  12. data/lib/active_model/serializer/array_serializer.rb +10 -5
  13. data/lib/active_model/serializer/association.rb +64 -10
  14. data/lib/active_model/serializer/attribute.rb +2 -0
  15. data/lib/active_model/serializer/belongs_to_reflection.rb +6 -3
  16. data/lib/active_model/serializer/collection_serializer.rb +48 -13
  17. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +89 -117
  18. data/lib/active_model/serializer/error_serializer.rb +13 -7
  19. data/lib/active_model/serializer/errors_serializer.rb +27 -20
  20. data/lib/active_model/serializer/field.rb +2 -0
  21. data/lib/active_model/serializer/fieldset.rb +3 -1
  22. data/lib/active_model/serializer/has_many_reflection.rb +5 -3
  23. data/lib/active_model/serializer/has_one_reflection.rb +3 -4
  24. data/lib/active_model/serializer/lazy_association.rb +99 -0
  25. data/lib/active_model/serializer/link.rb +23 -0
  26. data/lib/active_model/serializer/lint.rb +136 -130
  27. data/lib/active_model/serializer/null.rb +2 -0
  28. data/lib/active_model/serializer/reflection.rb +130 -65
  29. data/lib/active_model/serializer/version.rb +3 -1
  30. data/lib/active_model/serializer.rb +321 -86
  31. data/lib/active_model_serializers/adapter/attributes.rb +17 -57
  32. data/lib/active_model_serializers/adapter/base.rb +41 -39
  33. data/lib/active_model_serializers/adapter/json.rb +2 -0
  34. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +4 -2
  35. data/lib/active_model_serializers/adapter/json_api/error.rb +2 -0
  36. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +2 -0
  37. data/lib/active_model_serializers/adapter/json_api/link.rb +3 -1
  38. data/lib/active_model_serializers/adapter/json_api/meta.rb +2 -0
  39. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +52 -20
  40. data/lib/active_model_serializers/adapter/json_api/relationship.rb +77 -23
  41. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +41 -10
  42. data/lib/active_model_serializers/adapter/json_api.rb +84 -65
  43. data/lib/active_model_serializers/adapter/null.rb +2 -0
  44. data/lib/active_model_serializers/adapter.rb +9 -1
  45. data/lib/active_model_serializers/callbacks.rb +2 -0
  46. data/lib/active_model_serializers/deprecate.rb +3 -2
  47. data/lib/active_model_serializers/deserialization.rb +4 -0
  48. data/lib/active_model_serializers/json_pointer.rb +2 -0
  49. data/lib/active_model_serializers/logging.rb +2 -0
  50. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  51. data/lib/active_model_serializers/model/caching.rb +26 -0
  52. data/lib/active_model_serializers/model.rb +111 -28
  53. data/lib/active_model_serializers/railtie.rb +7 -1
  54. data/lib/active_model_serializers/register_jsonapi_renderer.rb +46 -31
  55. data/lib/active_model_serializers/serializable_resource.rb +10 -7
  56. data/lib/active_model_serializers/serialization_context.rb +12 -3
  57. data/lib/active_model_serializers/test/schema.rb +4 -2
  58. data/lib/active_model_serializers/test/serializer.rb +2 -0
  59. data/lib/active_model_serializers/test.rb +2 -0
  60. data/lib/active_model_serializers.rb +35 -10
  61. data/lib/generators/rails/resource_override.rb +3 -1
  62. data/lib/generators/rails/serializer_generator.rb +6 -4
  63. data/lib/grape/active_model_serializers.rb +9 -5
  64. data/lib/grape/formatters/active_model_serializers.rb +21 -2
  65. data/lib/grape/helpers/active_model_serializers.rb +3 -0
  66. data/lib/tasks/rubocop.rake +55 -0
  67. metadata +104 -296
  68. data/.github/ISSUE_TEMPLATE.md +0 -29
  69. data/.github/PULL_REQUEST_TEMPLATE.md +0 -15
  70. data/.gitignore +0 -35
  71. data/.rubocop.yml +0 -104
  72. data/.rubocop_todo.yml +0 -167
  73. data/.simplecov +0 -110
  74. data/.travis.yml +0 -43
  75. data/CONTRIBUTING.md +0 -105
  76. data/Gemfile +0 -53
  77. data/Rakefile +0 -103
  78. data/active_model_serializers.gemspec +0 -66
  79. data/appveyor.yml +0 -24
  80. data/bin/bench +0 -171
  81. data/bin/bench_regression +0 -316
  82. data/bin/serve_benchmark +0 -39
  83. data/docs/ARCHITECTURE.md +0 -126
  84. data/docs/README.md +0 -40
  85. data/docs/STYLE.md +0 -58
  86. data/docs/general/adapters.md +0 -245
  87. data/docs/general/caching.md +0 -52
  88. data/docs/general/configuration_options.md +0 -100
  89. data/docs/general/deserialization.md +0 -100
  90. data/docs/general/getting_started.md +0 -133
  91. data/docs/general/instrumentation.md +0 -40
  92. data/docs/general/key_transforms.md +0 -40
  93. data/docs/general/logging.md +0 -14
  94. data/docs/general/rendering.md +0 -255
  95. data/docs/general/serializers.md +0 -372
  96. data/docs/how-open-source-maintained.jpg +0 -0
  97. data/docs/howto/add_pagination_links.md +0 -139
  98. data/docs/howto/add_root_key.md +0 -51
  99. data/docs/howto/outside_controller_use.md +0 -58
  100. data/docs/howto/passing_arbitrary_options.md +0 -27
  101. data/docs/howto/serialize_poro.md +0 -32
  102. data/docs/howto/test.md +0 -152
  103. data/docs/integrations/ember-and-json-api.md +0 -112
  104. data/docs/integrations/grape.md +0 -19
  105. data/docs/jsonapi/errors.md +0 -56
  106. data/docs/jsonapi/schema/schema.json +0 -366
  107. data/docs/jsonapi/schema.md +0 -151
  108. data/docs/rfcs/0000-namespace.md +0 -106
  109. data/docs/rfcs/template.md +0 -15
  110. data/lib/active_model/serializer/associations.rb +0 -100
  111. data/lib/active_model/serializer/attributes.rb +0 -82
  112. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  113. data/lib/active_model/serializer/configuration.rb +0 -35
  114. data/lib/active_model/serializer/include_tree.rb +0 -111
  115. data/lib/active_model/serializer/links.rb +0 -35
  116. data/lib/active_model/serializer/meta.rb +0 -29
  117. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  118. data/lib/active_model/serializer/type.rb +0 -25
  119. data/lib/active_model_serializers/key_transform.rb +0 -70
  120. data/test/action_controller/adapter_selector_test.rb +0 -53
  121. data/test/action_controller/explicit_serializer_test.rb +0 -134
  122. data/test/action_controller/json/include_test.rb +0 -167
  123. data/test/action_controller/json_api/deserialization_test.rb +0 -112
  124. data/test/action_controller/json_api/errors_test.rb +0 -41
  125. data/test/action_controller/json_api/linked_test.rb +0 -197
  126. data/test/action_controller/json_api/pagination_test.rb +0 -116
  127. data/test/action_controller/json_api/transform_test.rb +0 -181
  128. data/test/action_controller/serialization_scope_name_test.rb +0 -229
  129. data/test/action_controller/serialization_test.rb +0 -469
  130. data/test/active_model_serializers/adapter_for_test.rb +0 -208
  131. data/test/active_model_serializers/json_pointer_test.rb +0 -20
  132. data/test/active_model_serializers/key_transform_test.rb +0 -263
  133. data/test/active_model_serializers/logging_test.rb +0 -77
  134. data/test/active_model_serializers/model_test.rb +0 -9
  135. data/test/active_model_serializers/railtie_test_isolated.rb +0 -63
  136. data/test/active_model_serializers/serialization_context_test_isolated.rb +0 -58
  137. data/test/active_model_serializers/test/schema_test.rb +0 -130
  138. data/test/active_model_serializers/test/serializer_test.rb +0 -62
  139. data/test/active_record_test.rb +0 -9
  140. data/test/adapter/deprecation_test.rb +0 -100
  141. data/test/adapter/json/belongs_to_test.rb +0 -45
  142. data/test/adapter/json/collection_test.rb +0 -90
  143. data/test/adapter/json/has_many_test.rb +0 -45
  144. data/test/adapter/json/transform_test.rb +0 -93
  145. data/test/adapter/json_api/belongs_to_test.rb +0 -155
  146. data/test/adapter/json_api/collection_test.rb +0 -95
  147. data/test/adapter/json_api/errors_test.rb +0 -78
  148. data/test/adapter/json_api/fields_test.rb +0 -87
  149. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  150. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +0 -96
  151. data/test/adapter/json_api/has_many_test.rb +0 -144
  152. data/test/adapter/json_api/has_one_test.rb +0 -80
  153. data/test/adapter/json_api/json_api_test.rb +0 -35
  154. data/test/adapter/json_api/linked_test.rb +0 -392
  155. data/test/adapter/json_api/links_test.rb +0 -93
  156. data/test/adapter/json_api/pagination_links_test.rb +0 -166
  157. data/test/adapter/json_api/parse_test.rb +0 -137
  158. data/test/adapter/json_api/relationship_test.rb +0 -161
  159. data/test/adapter/json_api/relationships_test.rb +0 -199
  160. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  161. data/test/adapter/json_api/resource_meta_test.rb +0 -100
  162. data/test/adapter/json_api/toplevel_jsonapi_test.rb +0 -82
  163. data/test/adapter/json_api/transform_test.rb +0 -502
  164. data/test/adapter/json_api/type_test.rb +0 -61
  165. data/test/adapter/json_test.rb +0 -45
  166. data/test/adapter/null_test.rb +0 -23
  167. data/test/adapter/polymorphic_test.rb +0 -171
  168. data/test/adapter_test.rb +0 -67
  169. data/test/array_serializer_test.rb +0 -22
  170. data/test/benchmark/app.rb +0 -65
  171. data/test/benchmark/benchmarking_support.rb +0 -67
  172. data/test/benchmark/bm_caching.rb +0 -119
  173. data/test/benchmark/bm_transform.rb +0 -34
  174. data/test/benchmark/config.ru +0 -3
  175. data/test/benchmark/controllers.rb +0 -84
  176. data/test/benchmark/fixtures.rb +0 -219
  177. data/test/cache_test.rb +0 -485
  178. data/test/collection_serializer_test.rb +0 -110
  179. data/test/fixtures/active_record.rb +0 -78
  180. data/test/fixtures/poro.rb +0 -282
  181. data/test/generators/scaffold_controller_generator_test.rb +0 -24
  182. data/test/generators/serializer_generator_test.rb +0 -57
  183. data/test/grape_test.rb +0 -82
  184. data/test/include_tree/from_include_args_test.rb +0 -26
  185. data/test/include_tree/from_string_test.rb +0 -94
  186. data/test/include_tree/include_args_to_hash_test.rb +0 -64
  187. data/test/lint_test.rb +0 -49
  188. data/test/logger_test.rb +0 -18
  189. data/test/poro_test.rb +0 -9
  190. data/test/serializable_resource_test.rb +0 -83
  191. data/test/serializers/association_macros_test.rb +0 -36
  192. data/test/serializers/associations_test.rb +0 -295
  193. data/test/serializers/attribute_test.rb +0 -151
  194. data/test/serializers/attributes_test.rb +0 -52
  195. data/test/serializers/caching_configuration_test_isolated.rb +0 -170
  196. data/test/serializers/configuration_test.rb +0 -32
  197. data/test/serializers/fieldset_test.rb +0 -14
  198. data/test/serializers/meta_test.rb +0 -196
  199. data/test/serializers/options_test.rb +0 -21
  200. data/test/serializers/read_attribute_for_serialization_test.rb +0 -79
  201. data/test/serializers/root_test.rb +0 -21
  202. data/test/serializers/serialization_test.rb +0 -55
  203. data/test/serializers/serializer_for_test.rb +0 -134
  204. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +0 -6
  205. data/test/support/isolated_unit.rb +0 -79
  206. data/test/support/rails5_shims.rb +0 -47
  207. data/test/support/rails_app.rb +0 -45
  208. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +0 -6
  209. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +0 -6
  210. data/test/support/schemas/custom/show.json +0 -7
  211. data/test/support/schemas/hyper_schema.json +0 -93
  212. data/test/support/schemas/render_using_json_api.json +0 -43
  213. data/test/support/schemas/simple_json_pointers.json +0 -10
  214. data/test/support/serialization_testing.rb +0 -53
  215. data/test/test_helper.rb +0 -57
@@ -1,9 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model/serializer/collection_serializer'
2
- class ActiveModel::Serializer
3
- class ArraySerializer < CollectionSerializer
4
- class << self
5
- extend ActiveModelSerializers::Deprecate
6
- deprecate :new, 'ActiveModel::Serializer::CollectionSerializer.'
4
+
5
+ module ActiveModel
6
+ class Serializer
7
+ class ArraySerializer < CollectionSerializer
8
+ class << self
9
+ extend ActiveModelSerializers::Deprecate
10
+ deprecate :new, 'ActiveModel::Serializer::CollectionSerializer.'
11
+ end
7
12
  end
8
13
  end
9
14
  end
@@ -1,19 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/serializer/lazy_association'
4
+
1
5
  module ActiveModel
2
6
  class Serializer
3
- # This class hold all information about serializer's association.
7
+ # This class holds all information about serializer's association.
4
8
  #
5
- # @attr [Symbol] name
6
- # @attr [ActiveModel::Serializer] serializer
7
- # @attr [Hash{Symbol => Object}] options
8
- #
9
- # @example
10
- # Association.new(:comments, CommentSummarySerializer)
11
- #
12
- Association = Struct.new(:name, :serializer, :options, :links, :meta) do
9
+ # @api private
10
+ Association = Struct.new(:reflection, :association_options) do
11
+ attr_reader :lazy_association
12
+ delegate :object, :include_data?, :virtual_value, :collection?, to: :lazy_association
13
+
14
+ def initialize(*)
15
+ super
16
+ @lazy_association = LazyAssociation.new(reflection, association_options)
17
+ end
18
+
19
+ # @return [Symbol]
20
+ delegate :name, to: :reflection
21
+
13
22
  # @return [Symbol]
14
23
  def key
15
- options.fetch(:key, name)
24
+ reflection_options.fetch(:key, name)
25
+ end
26
+
27
+ # @return [True,False]
28
+ def key?
29
+ reflection_options.key?(:key)
30
+ end
31
+
32
+ # @return [Hash]
33
+ def links
34
+ reflection_options.fetch(:links) || {}
35
+ end
36
+
37
+ # @return [Hash, nil]
38
+ # This gets mutated, so cannot use the cached reflection_options
39
+ def meta
40
+ reflection.options[:meta]
41
+ end
42
+
43
+ def belongs_to?
44
+ reflection.foreign_key_on == :self
45
+ end
46
+
47
+ def polymorphic?
48
+ true == reflection_options[:polymorphic]
49
+ end
50
+
51
+ # @api private
52
+ def serializable_hash(adapter_options, adapter_instance)
53
+ association_serializer = lazy_association.serializer
54
+ return virtual_value if virtual_value
55
+ association_object = association_serializer && association_serializer.object
56
+ return unless association_object
57
+
58
+ serialization = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
59
+
60
+ if polymorphic? && serialization
61
+ polymorphic_type = association_object.class.name.underscore
62
+ serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization }
63
+ end
64
+
65
+ serialization
16
66
  end
67
+
68
+ private
69
+
70
+ delegate :reflection_options, to: :lazy_association
17
71
  end
18
72
  end
19
73
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model/serializer/field'
2
4
 
3
5
  module ActiveModel
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  class Serializer
3
5
  # @api private
4
- class BelongsToReflection < SingularReflection
5
- def macro
6
- :belongs_to
6
+ class BelongsToReflection < Reflection
7
+ # @api private
8
+ def foreign_key_on
9
+ :self
7
10
  end
8
11
  end
9
12
  end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  class Serializer
3
5
  class CollectionSerializer
4
- NoSerializerError = Class.new(StandardError)
5
6
  include Enumerable
6
7
  delegate :each, to: :@serializers
7
8
 
@@ -11,22 +12,22 @@ module ActiveModel
11
12
  @object = resources
12
13
  @options = options
13
14
  @root = options[:root]
14
- serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
15
- @serializers = resources.map do |resource|
16
- serializer_class = options.fetch(:serializer) { serializer_context_class.serializer_for(resource) }
17
-
18
- if serializer_class.nil? # rubocop:disable Style/GuardClause
19
- fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
20
- else
21
- serializer_class.new(resource, options.except(:serializer))
22
- end
23
- end
15
+ @serializers = serializers_from_resources
24
16
  end
25
17
 
26
18
  def success?
27
19
  true
28
20
  end
29
21
 
22
+ # @api private
23
+ def serializable_hash(adapter_options, options, adapter_instance)
24
+ options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
25
+ options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(self, adapter_instance, options[:include_directive])
26
+ serializers.map do |serializer|
27
+ serializer.serializable_hash(adapter_options, options, adapter_instance)
28
+ end
29
+ end
30
+
30
31
  # TODO: unify naming of root, json_key, and _type. Right now, a serializer's
31
32
  # json_key comes from the root option or the object's model name, by default.
32
33
  # But, if a dev defines a custom `json_key` method with an explicit value,
@@ -46,19 +47,53 @@ module ActiveModel
46
47
  # 3. get from collection name, if a named collection
47
48
  key ||= object.respond_to?(:name) ? object.name && object.name.underscore : nil
48
49
  # 4. key may be nil for empty collection and no serializer option
49
- key && key.pluralize
50
+ key &&= key.pluralize
51
+ if raise_cannot_infer_root_key_error?
52
+ # 5. fail if the key cannot be determined
53
+ key || fail(CannotInferRootKeyError, 'Cannot infer root key from collection type. Please specify the root or each_serializer option, or render a JSON String')
54
+ end
55
+ key
50
56
  end
51
57
  # rubocop:enable Metrics/CyclomaticComplexity
52
58
 
53
59
  def paginated?
54
- object.respond_to?(:current_page) &&
60
+ ActiveModelSerializers.config.jsonapi_pagination_links_enabled &&
61
+ object.respond_to?(:current_page) &&
55
62
  object.respond_to?(:total_pages) &&
56
63
  object.respond_to?(:size)
57
64
  end
58
65
 
66
+ class CannotInferRootKeyError < StandardError; end
67
+
59
68
  protected
60
69
 
61
70
  attr_reader :serializers, :options
71
+
72
+ private
73
+
74
+ def raise_cannot_infer_root_key_error?
75
+ ActiveModelSerializers.config.raise_cannot_infer_root_key_error
76
+ end
77
+
78
+ def serializers_from_resources
79
+ serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
80
+ object.map do |resource|
81
+ serializer_from_resource(resource, serializer_context_class, options)
82
+ end
83
+ end
84
+
85
+ def serializer_from_resource(resource, serializer_context_class, options)
86
+ serializer_class = options.fetch(:serializer) do
87
+ serializer_context_class.serializer_for(resource, namespace: options[:namespace])
88
+ end
89
+
90
+ if serializer_class.nil?
91
+ ActiveModelSerializers.logger.debug "No serializer found for resource: #{resource.inspect}"
92
+ throw :no_serializer
93
+ else
94
+ serializer_class.new(resource, options.except(:serializer))
95
+ end
96
+ end
62
97
  end
63
98
  end
64
99
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  class Serializer
3
5
  UndefinedCacheKey = Class.new(StandardError)
@@ -7,10 +9,9 @@ module ActiveModel
7
9
  included do
8
10
  with_options instance_writer: false, instance_reader: false do |serializer|
9
11
  serializer.class_attribute :_cache # @api private : the cache store
10
- serializer.class_attribute :_fragmented # @api private : @see ::fragmented
11
12
  serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key. Ignored if the serializable object defines #cache_key.
12
- serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists cached_attributes. Cannot combine with except
13
- serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists cached_attributes. Cannot combine with only
13
+ serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists fetch_attributes. Cannot combine with except
14
+ serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists fetch_attributes. Cannot combine with only
14
15
  serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
15
16
  # _cache_options include:
16
17
  # expires_in
@@ -19,7 +20,7 @@ module ActiveModel
19
20
  # race_condition_ttl
20
21
  # Passed to ::_cache as
21
22
  # 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
+ # Passed as second argument to serializer.cache_store.fetch(cache_key, serializer_class._cache_options)
23
24
  serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
24
25
  end
25
26
  end
@@ -41,9 +42,9 @@ module ActiveModel
41
42
 
42
43
  module ClassMethods
43
44
  def inherited(base)
44
- super
45
45
  caller_line = caller[1]
46
46
  base._cache_digest_file_path = caller_line
47
+ super
47
48
  end
48
49
 
49
50
  def _cache_digest
@@ -55,7 +56,8 @@ module ActiveModel
55
56
  def digest_caller_file(caller_line)
56
57
  serializer_file_path = caller_line[CALLER_FILE]
57
58
  serializer_file_contents = IO.read(serializer_file_path)
58
- Digest::MD5.hexdigest(serializer_file_contents)
59
+ algorithm = ActiveModelSerializers.config.use_sha1_digests ? Digest::SHA1 : Digest::MD5
60
+ algorithm.hexdigest(serializer_file_contents)
59
61
  rescue TypeError, Errno::ENOENT
60
62
  warn <<-EOF.strip_heredoc
61
63
  Cannot digest non-existent file: '#{caller_line}'.
@@ -69,19 +71,27 @@ module ActiveModel
69
71
  _cache_options && _cache_options[:skip_digest]
70
72
  end
71
73
 
72
- def cached_attributes
73
- _cache_only ? _cache_only : _attributes - _cache_except
74
- end
75
-
76
- def non_cached_attributes
77
- _attributes - cached_attributes
74
+ # @api private
75
+ # maps attribute value to explicit key name
76
+ # @see Serializer::attribute
77
+ # @see Serializer::fragmented_attributes
78
+ def _attributes_keys
79
+ _attributes_data
80
+ .each_with_object({}) do |(key, attr), hash|
81
+ next if key == attr.name
82
+ hash[attr.name] = { key: key }
83
+ end
78
84
  end
79
85
 
80
- # @api private
81
- # Used by FragmentCache on the CachedSerializer
82
- # to call attribute methods on the fragmented cached serializer.
83
- def fragmented(serializer)
84
- self._fragmented = serializer
86
+ def fragmented_attributes
87
+ cached = _cache_only ? _cache_only : _attributes - _cache_except
88
+ cached = cached.map! { |field| _attributes_keys.fetch(field, field) }
89
+ non_cached = _attributes - cached
90
+ non_cached = non_cached.map! { |field| _attributes_keys.fetch(field, field) }
91
+ {
92
+ cached: cached,
93
+ non_cached: non_cached
94
+ }
85
95
  end
86
96
 
87
97
  # Enables a serializer to be automatically cached
@@ -163,10 +173,11 @@ module ActiveModel
163
173
 
164
174
  # Read cache from cache_store
165
175
  # @return [Hash]
166
- def cache_read_multi(collection_serializer, adapter_instance, include_tree)
176
+ # Used in CollectionSerializer to set :cached_attributes
177
+ def cache_read_multi(collection_serializer, adapter_instance, include_directive)
167
178
  return {} if ActiveModelSerializers.config.cache_store.blank?
168
179
 
169
- keys = object_cache_keys(collection_serializer, adapter_instance, include_tree)
180
+ keys = object_cache_keys(collection_serializer, adapter_instance, include_directive)
170
181
 
171
182
  return {} if keys.blank?
172
183
 
@@ -176,21 +187,23 @@ module ActiveModel
176
187
  # Find all cache_key for the collection_serializer
177
188
  # @param serializers [ActiveModel::Serializer::CollectionSerializer]
178
189
  # @param adapter_instance [ActiveModelSerializers::Adapter::Base]
179
- # @param include_tree [ActiveModel::Serializer::IncludeTree]
190
+ # @param include_directive [JSONAPI::IncludeDirective]
180
191
  # @return [Array] all cache_key of collection_serializer
181
- def object_cache_keys(collection_serializer, adapter_instance, include_tree)
192
+ def object_cache_keys(collection_serializer, adapter_instance, include_directive)
182
193
  cache_keys = []
183
194
 
184
195
  collection_serializer.each do |serializer|
185
196
  cache_keys << object_cache_key(serializer, adapter_instance)
186
197
 
187
- serializer.associations(include_tree).each do |association|
188
- if association.serializer.respond_to?(:each)
189
- association.serializer.each do |sub_serializer|
198
+ serializer.associations(include_directive).each do |association|
199
+ # TODO(BF): Process relationship without evaluating lazy_association
200
+ association_serializer = association.lazy_association.serializer
201
+ if association_serializer.respond_to?(:each)
202
+ association_serializer.each do |sub_serializer|
190
203
  cache_keys << object_cache_key(sub_serializer, adapter_instance)
191
204
  end
192
205
  else
193
- cache_keys << object_cache_key(association.serializer, adapter_instance)
206
+ cache_keys << object_cache_key(association_serializer, adapter_instance)
194
207
  end
195
208
  end
196
209
  end
@@ -202,132 +215,91 @@ module ActiveModel
202
215
  def object_cache_key(serializer, adapter_instance)
203
216
  return unless serializer.present? && serializer.object.present?
204
217
 
205
- serializer.class.cache_enabled? ? serializer.cache_key(adapter_instance) : nil
218
+ (serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil
206
219
  end
207
220
  end
208
221
 
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)
222
+ ### INSTANCE METHODS
223
+ def fetch_attributes(fields, cached_attributes, adapter_instance)
224
+ key = cache_key(adapter_instance)
225
+ cached_attributes.fetch(key) do
226
+ fetch(adapter_instance, serializer_class._cache_options, key) do
227
+ attributes(fields, true)
228
+ end
215
229
  end
216
230
  end
217
231
 
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
232
+ def fetch(adapter_instance, cache_options = serializer_class._cache_options, key = nil)
233
+ if serializer_class.cache_store
234
+ key ||= cache_key(adapter_instance)
235
+ serializer_class.cache_store.fetch(key, cache_options) do
221
236
  yield
222
237
  end
223
- elsif self.class.fragment_cache_enabled?
224
- fetch_fragment_cache(adapter_instance)
225
238
  else
226
239
  yield
227
240
  end
228
241
  end
229
242
 
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
-
243
+ # 1. Determine cached fields from serializer class options
244
+ # 2. Get non_cached_fields and fetch cache_fields
245
+ # 3. Merge the two hashes using adapter_instance#fragment_cache
246
+ def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
247
+ serializer_class._cache_options ||= {}
248
+ serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
249
+ fields = serializer_class.fragmented_attributes
250
+
251
+ non_cached_fields = fields[:non_cached].dup
252
+ non_cached_hash = attributes(non_cached_fields, true)
253
+ include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
254
+ non_cached_hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
255
+
256
+ cached_fields = fields[:cached].dup
257
+ key = cache_key(adapter_instance)
258
+ cached_hash =
259
+ cached_attributes.fetch(key) do
260
+ fetch(adapter_instance, serializer_class._cache_options, key) do
261
+ hash = attributes(cached_fields, true)
262
+ include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
263
+ hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
264
+ end
265
+ end
276
266
  # Merge both results
277
267
  adapter_instance.fragment_cache(cached_hash, non_cached_hash)
278
268
  end
279
269
 
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
270
  def cache_key(adapter_instance)
309
271
  return @cache_key if defined?(@cache_key)
310
272
 
311
273
  parts = []
312
274
  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('/')
275
+ parts << adapter_instance.cache_key
276
+ parts << serializer_class._cache_digest unless serializer_class._skip_digest?
277
+ @cache_key = expand_cache_key(parts)
278
+ end
279
+
280
+ def expand_cache_key(parts)
281
+ ActiveSupport::Cache.expand_cache_key(parts)
316
282
  end
317
283
 
318
284
  # Use object's cache_key if available, else derive a key from the object
319
285
  # Pass the `key` option to the `cache` declaration or override this method to customize the cache key
320
286
  def object_cache_key
321
- if object.respond_to?(:cache_key)
287
+ if object.respond_to?(:cache_key_with_version)
288
+ object.cache_key_with_version
289
+ elsif object.respond_to?(:cache_key)
322
290
  object.cache_key
323
- elsif (serializer_cache_key = (self.class._cache_key || self.class._cache_options[:key]))
291
+ elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key]))
324
292
  object_time_safe = object.updated_at
325
293
  object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
326
294
  "#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
327
295
  else
328
- fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{self.class}.cache'"
296
+ fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{serializer_class}.cache'"
329
297
  end
330
298
  end
299
+
300
+ def serializer_class
301
+ @serializer_class ||= self.class
302
+ end
331
303
  end
332
304
  end
333
305
  end
@@ -1,10 +1,16 @@
1
- class ActiveModel::Serializer::ErrorSerializer < ActiveModel::Serializer
2
- # @return [Hash<field_name,Array<error_message>>]
3
- def as_json
4
- object.errors.messages
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class ErrorSerializer < ActiveModel::Serializer
6
+ # @return [Hash<field_name,Array<error_message>>]
7
+ def as_json
8
+ object.errors.messages
9
+ end
6
10
 
7
- def success?
8
- false
11
+ def success?
12
+ false
13
+ end
14
+ end
9
15
  end
10
16
  end
@@ -1,27 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model/serializer/error_serializer'
2
- class ActiveModel::Serializer::ErrorsSerializer
3
- include Enumerable
4
- delegate :each, to: :@serializers
5
- attr_reader :object, :root
6
4
 
7
- def initialize(resources, options = {})
8
- @root = options[:root]
9
- @object = resources
10
- @serializers = resources.map do |resource|
11
- serializer_class = options.fetch(:serializer) { ActiveModel::Serializer::ErrorSerializer }
12
- serializer_class.new(resource, options.except(:serializer))
13
- end
14
- end
5
+ module ActiveModel
6
+ class Serializer
7
+ class ErrorsSerializer
8
+ include Enumerable
9
+ delegate :each, to: :@serializers
10
+ attr_reader :object, :root
15
11
 
16
- def success?
17
- false
18
- end
12
+ def initialize(resources, options = {})
13
+ @root = options[:root]
14
+ @object = resources
15
+ @serializers = resources.map do |resource|
16
+ serializer_class = options.fetch(:serializer) { ActiveModel::Serializer::ErrorSerializer }
17
+ serializer_class.new(resource, options.except(:serializer))
18
+ end
19
+ end
19
20
 
20
- def json_key
21
- nil
22
- end
21
+ def success?
22
+ false
23
+ end
24
+
25
+ def json_key
26
+ nil
27
+ end
23
28
 
24
- protected
29
+ protected
25
30
 
26
- attr_reader :serializers
31
+ attr_reader :serializers
32
+ end
33
+ end
27
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  class Serializer
3
5
  # Holds all the meta-data about a field (i.e. attribute or association) as it was
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  class Serializer
3
5
  class Fieldset
@@ -10,7 +12,7 @@ module ActiveModel
10
12
  end
11
13
 
12
14
  def fields_for(type)
13
- fields[type.singularize.to_sym] || fields[type.pluralize.to_sym]
15
+ fields[type.to_s.singularize.to_sym] || fields[type.to_s.pluralize.to_sym]
14
16
  end
15
17
 
16
18
  protected
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  class Serializer
3
5
  # @api private
4
- class HasManyReflection < CollectionReflection
5
- def macro
6
- :has_many
6
+ class HasManyReflection < Reflection
7
+ def collection?
8
+ true
7
9
  end
8
10
  end
9
11
  end