active_model_serializers 0.10.0 → 0.10.7

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 (171) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +6 -5
  3. data/.travis.yml +30 -21
  4. data/CHANGELOG.md +172 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +23 -4
  7. data/README.md +166 -28
  8. data/Rakefile +3 -32
  9. data/active_model_serializers.gemspec +22 -25
  10. data/appveyor.yml +10 -6
  11. data/bin/rubocop +38 -0
  12. data/docs/README.md +2 -1
  13. data/docs/general/adapters.md +35 -11
  14. data/docs/general/caching.md +7 -1
  15. data/docs/general/configuration_options.md +86 -1
  16. data/docs/general/deserialization.md +1 -1
  17. data/docs/general/fields.md +31 -0
  18. data/docs/general/getting_started.md +1 -1
  19. data/docs/general/logging.md +7 -0
  20. data/docs/general/rendering.md +63 -25
  21. data/docs/general/serializers.md +125 -14
  22. data/docs/howto/add_pagination_links.md +16 -17
  23. data/docs/howto/add_relationship_links.md +140 -0
  24. data/docs/howto/add_root_key.md +11 -0
  25. data/docs/howto/grape_integration.md +42 -0
  26. data/docs/howto/outside_controller_use.md +12 -4
  27. data/docs/howto/passing_arbitrary_options.md +2 -2
  28. data/docs/howto/serialize_poro.md +46 -5
  29. data/docs/howto/test.md +2 -0
  30. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  31. data/docs/integrations/ember-and-json-api.md +67 -32
  32. data/docs/jsonapi/schema.md +1 -1
  33. data/lib/action_controller/serialization.rb +13 -3
  34. data/lib/active_model/serializer/adapter/base.rb +2 -0
  35. data/lib/active_model/serializer/array_serializer.rb +8 -5
  36. data/lib/active_model/serializer/association.rb +62 -10
  37. data/lib/active_model/serializer/belongs_to_reflection.rb +4 -3
  38. data/lib/active_model/serializer/collection_serializer.rb +39 -13
  39. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +82 -115
  40. data/lib/active_model/serializer/error_serializer.rb +11 -7
  41. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  42. data/lib/active_model/serializer/has_many_reflection.rb +3 -3
  43. data/lib/active_model/serializer/has_one_reflection.rb +1 -4
  44. data/lib/active_model/serializer/lazy_association.rb +95 -0
  45. data/lib/active_model/serializer/lint.rb +134 -130
  46. data/lib/active_model/serializer/reflection.rb +127 -67
  47. data/lib/active_model/serializer/version.rb +1 -1
  48. data/lib/active_model/serializer.rb +297 -79
  49. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  50. data/lib/active_model_serializers/adapter/base.rb +39 -39
  51. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +2 -2
  52. data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
  53. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +47 -21
  54. data/lib/active_model_serializers/adapter/json_api/relationship.rb +75 -23
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +39 -10
  56. data/lib/active_model_serializers/adapter/json_api.rb +71 -57
  57. data/lib/active_model_serializers/adapter.rb +6 -0
  58. data/lib/active_model_serializers/deprecate.rb +1 -2
  59. data/lib/active_model_serializers/deserialization.rb +2 -0
  60. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  61. data/lib/active_model_serializers/model.rb +109 -28
  62. data/lib/active_model_serializers/railtie.rb +3 -1
  63. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  64. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  65. data/lib/active_model_serializers/serialization_context.rb +10 -3
  66. data/lib/active_model_serializers/test/schema.rb +2 -2
  67. data/lib/active_model_serializers.rb +16 -1
  68. data/lib/generators/rails/resource_override.rb +1 -1
  69. data/lib/generators/rails/serializer_generator.rb +4 -4
  70. data/lib/grape/active_model_serializers.rb +7 -5
  71. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  72. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  73. data/lib/tasks/rubocop.rake +53 -0
  74. data/test/action_controller/adapter_selector_test.rb +14 -5
  75. data/test/action_controller/explicit_serializer_test.rb +5 -4
  76. data/test/action_controller/json/include_test.rb +106 -27
  77. data/test/action_controller/json_api/deserialization_test.rb +1 -1
  78. data/test/action_controller/json_api/errors_test.rb +8 -9
  79. data/test/action_controller/json_api/fields_test.rb +66 -0
  80. data/test/action_controller/json_api/linked_test.rb +29 -24
  81. data/test/action_controller/json_api/pagination_test.rb +31 -23
  82. data/test/action_controller/json_api/transform_test.rb +11 -3
  83. data/test/action_controller/lookup_proc_test.rb +49 -0
  84. data/test/action_controller/namespace_lookup_test.rb +232 -0
  85. data/test/action_controller/serialization_scope_name_test.rb +12 -6
  86. data/test/action_controller/serialization_test.rb +12 -9
  87. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  88. data/test/active_model_serializers/model_test.rb +137 -4
  89. data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
  90. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  91. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  92. data/test/active_model_serializers/test/schema_test.rb +3 -2
  93. data/test/adapter/attributes_test.rb +40 -0
  94. data/test/adapter/json/collection_test.rb +14 -0
  95. data/test/adapter/json/has_many_test.rb +10 -2
  96. data/test/adapter/json/transform_test.rb +15 -15
  97. data/test/adapter/json_api/collection_test.rb +4 -3
  98. data/test/adapter/json_api/errors_test.rb +17 -19
  99. data/test/adapter/json_api/fields_test.rb +12 -3
  100. data/test/adapter/json_api/has_many_test.rb +49 -20
  101. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
  102. data/test/adapter/json_api/json_api_test.rb +5 -7
  103. data/test/adapter/json_api/linked_test.rb +33 -12
  104. data/test/adapter/json_api/links_test.rb +4 -2
  105. data/test/adapter/json_api/pagination_links_test.rb +53 -13
  106. data/test/adapter/json_api/parse_test.rb +1 -1
  107. data/test/adapter/json_api/relationship_test.rb +309 -73
  108. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  109. data/test/adapter/json_api/transform_test.rb +263 -253
  110. data/test/adapter/json_api/type_test.rb +168 -36
  111. data/test/adapter/json_test.rb +8 -7
  112. data/test/adapter/null_test.rb +1 -2
  113. data/test/adapter/polymorphic_test.rb +52 -5
  114. data/test/adapter_test.rb +1 -1
  115. data/test/benchmark/app.rb +1 -1
  116. data/test/benchmark/benchmarking_support.rb +1 -1
  117. data/test/benchmark/bm_active_record.rb +81 -0
  118. data/test/benchmark/bm_adapter.rb +38 -0
  119. data/test/benchmark/bm_caching.rb +16 -16
  120. data/test/benchmark/bm_lookup_chain.rb +83 -0
  121. data/test/benchmark/bm_transform.rb +21 -10
  122. data/test/benchmark/controllers.rb +16 -17
  123. data/test/benchmark/fixtures.rb +72 -72
  124. data/test/cache_test.rb +235 -69
  125. data/test/collection_serializer_test.rb +31 -14
  126. data/test/fixtures/active_record.rb +45 -10
  127. data/test/fixtures/poro.rb +124 -181
  128. data/test/generators/serializer_generator_test.rb +23 -5
  129. data/test/grape_test.rb +170 -56
  130. data/test/lint_test.rb +1 -1
  131. data/test/logger_test.rb +13 -11
  132. data/test/serializable_resource_test.rb +18 -22
  133. data/test/serializers/association_macros_test.rb +3 -2
  134. data/test/serializers/associations_test.rb +222 -49
  135. data/test/serializers/attribute_test.rb +5 -3
  136. data/test/serializers/attributes_test.rb +1 -1
  137. data/test/serializers/caching_configuration_test_isolated.rb +6 -6
  138. data/test/serializers/fieldset_test.rb +1 -1
  139. data/test/serializers/meta_test.rb +12 -6
  140. data/test/serializers/options_test.rb +17 -6
  141. data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
  142. data/test/serializers/reflection_test.rb +427 -0
  143. data/test/serializers/root_test.rb +1 -1
  144. data/test/serializers/serialization_test.rb +2 -2
  145. data/test/serializers/serializer_for_test.rb +12 -10
  146. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  147. data/test/support/isolated_unit.rb +9 -4
  148. data/test/support/rails5_shims.rb +8 -2
  149. data/test/support/rails_app.rb +2 -9
  150. data/test/support/serialization_testing.rb +31 -5
  151. data/test/test_helper.rb +13 -0
  152. metadata +130 -71
  153. data/.rubocop_todo.yml +0 -167
  154. data/docs/ARCHITECTURE.md +0 -126
  155. data/lib/active_model/serializer/associations.rb +0 -100
  156. data/lib/active_model/serializer/attributes.rb +0 -82
  157. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  158. data/lib/active_model/serializer/configuration.rb +0 -35
  159. data/lib/active_model/serializer/include_tree.rb +0 -111
  160. data/lib/active_model/serializer/links.rb +0 -35
  161. data/lib/active_model/serializer/meta.rb +0 -29
  162. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  163. data/lib/active_model/serializer/type.rb +0 -25
  164. data/lib/active_model_serializers/key_transform.rb +0 -70
  165. data/test/active_model_serializers/key_transform_test.rb +0 -263
  166. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  167. data/test/adapter/json_api/relationships_test.rb +0 -199
  168. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  169. data/test/include_tree/from_include_args_test.rb +0 -26
  170. data/test/include_tree/from_string_test.rb +0 -94
  171. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -7,10 +7,9 @@ module ActiveModel
7
7
  included do
8
8
  with_options instance_writer: false, instance_reader: false do |serializer|
9
9
  serializer.class_attribute :_cache # @api private : the cache store
10
- serializer.class_attribute :_fragmented # @api private : @see ::fragmented
11
10
  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
11
+ serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists fetch_attributes. Cannot combine with except
12
+ serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists fetch_attributes. Cannot combine with only
14
13
  serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
15
14
  # _cache_options include:
16
15
  # expires_in
@@ -19,7 +18,7 @@ module ActiveModel
19
18
  # race_condition_ttl
20
19
  # Passed to ::_cache as
21
20
  # 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)
21
+ # Passed as second argument to serializer.cache_store.fetch(cache_key, serializer_class._cache_options)
23
22
  serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
24
23
  end
25
24
  end
@@ -41,9 +40,9 @@ module ActiveModel
41
40
 
42
41
  module ClassMethods
43
42
  def inherited(base)
44
- super
45
43
  caller_line = caller[1]
46
44
  base._cache_digest_file_path = caller_line
45
+ super
47
46
  end
48
47
 
49
48
  def _cache_digest
@@ -69,19 +68,27 @@ module ActiveModel
69
68
  _cache_options && _cache_options[:skip_digest]
70
69
  end
71
70
 
72
- def cached_attributes
73
- _cache_only ? _cache_only : _attributes - _cache_except
74
- end
75
-
76
- def non_cached_attributes
77
- _attributes - cached_attributes
71
+ # @api private
72
+ # maps attribute value to explicit key name
73
+ # @see Serializer::attribute
74
+ # @see Serializer::fragmented_attributes
75
+ def _attributes_keys
76
+ _attributes_data
77
+ .each_with_object({}) do |(key, attr), hash|
78
+ next if key == attr.name
79
+ hash[attr.name] = { key: key }
80
+ end
78
81
  end
79
82
 
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
83
+ def fragmented_attributes
84
+ cached = _cache_only ? _cache_only : _attributes - _cache_except
85
+ cached = cached.map! { |field| _attributes_keys.fetch(field, field) }
86
+ non_cached = _attributes - cached
87
+ non_cached = non_cached.map! { |field| _attributes_keys.fetch(field, field) }
88
+ {
89
+ cached: cached,
90
+ non_cached: non_cached
91
+ }
85
92
  end
86
93
 
87
94
  # Enables a serializer to be automatically cached
@@ -163,10 +170,11 @@ module ActiveModel
163
170
 
164
171
  # Read cache from cache_store
165
172
  # @return [Hash]
166
- def cache_read_multi(collection_serializer, adapter_instance, include_tree)
173
+ # Used in CollectionSerializer to set :cached_attributes
174
+ def cache_read_multi(collection_serializer, adapter_instance, include_directive)
167
175
  return {} if ActiveModelSerializers.config.cache_store.blank?
168
176
 
169
- keys = object_cache_keys(collection_serializer, adapter_instance, include_tree)
177
+ keys = object_cache_keys(collection_serializer, adapter_instance, include_directive)
170
178
 
171
179
  return {} if keys.blank?
172
180
 
@@ -176,21 +184,23 @@ module ActiveModel
176
184
  # Find all cache_key for the collection_serializer
177
185
  # @param serializers [ActiveModel::Serializer::CollectionSerializer]
178
186
  # @param adapter_instance [ActiveModelSerializers::Adapter::Base]
179
- # @param include_tree [ActiveModel::Serializer::IncludeTree]
187
+ # @param include_directive [JSONAPI::IncludeDirective]
180
188
  # @return [Array] all cache_key of collection_serializer
181
- def object_cache_keys(collection_serializer, adapter_instance, include_tree)
189
+ def object_cache_keys(collection_serializer, adapter_instance, include_directive)
182
190
  cache_keys = []
183
191
 
184
192
  collection_serializer.each do |serializer|
185
193
  cache_keys << object_cache_key(serializer, adapter_instance)
186
194
 
187
- serializer.associations(include_tree).each do |association|
188
- if association.serializer.respond_to?(:each)
189
- association.serializer.each do |sub_serializer|
195
+ serializer.associations(include_directive).each do |association|
196
+ # TODO(BF): Process relationship without evaluating lazy_association
197
+ association_serializer = association.lazy_association.serializer
198
+ if association_serializer.respond_to?(:each)
199
+ association_serializer.each do |sub_serializer|
190
200
  cache_keys << object_cache_key(sub_serializer, adapter_instance)
191
201
  end
192
202
  else
193
- cache_keys << object_cache_key(association.serializer, adapter_instance)
203
+ cache_keys << object_cache_key(association_serializer, adapter_instance)
194
204
  end
195
205
  end
196
206
  end
@@ -202,117 +212,70 @@ module ActiveModel
202
212
  def object_cache_key(serializer, adapter_instance)
203
213
  return unless serializer.present? && serializer.object.present?
204
214
 
205
- serializer.class.cache_enabled? ? serializer.cache_key(adapter_instance) : nil
215
+ (serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil
206
216
  end
207
217
  end
208
218
 
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)
219
+ ### INSTANCE METHODS
220
+ def fetch_attributes(fields, cached_attributes, adapter_instance)
221
+ key = cache_key(adapter_instance)
222
+ cached_attributes.fetch(key) do
223
+ fetch(adapter_instance, serializer_class._cache_options, key) do
224
+ attributes(fields, true)
225
+ end
215
226
  end
216
227
  end
217
228
 
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
229
+ def fetch(adapter_instance, cache_options = serializer_class._cache_options, key = nil)
230
+ if serializer_class.cache_store
231
+ key ||= cache_key(adapter_instance)
232
+ serializer_class.cache_store.fetch(key, cache_options) do
221
233
  yield
222
234
  end
223
- elsif self.class.fragment_cache_enabled?
224
- fetch_fragment_cache(adapter_instance)
225
235
  else
226
236
  yield
227
237
  end
228
238
  end
229
239
 
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
-
240
+ # 1. Determine cached fields from serializer class options
241
+ # 2. Get non_cached_fields and fetch cache_fields
242
+ # 3. Merge the two hashes using adapter_instance#fragment_cache
243
+ def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
244
+ serializer_class._cache_options ||= {}
245
+ serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
246
+ fields = serializer_class.fragmented_attributes
247
+
248
+ non_cached_fields = fields[:non_cached].dup
249
+ non_cached_hash = attributes(non_cached_fields, true)
250
+ include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
251
+ non_cached_hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
252
+
253
+ cached_fields = fields[:cached].dup
254
+ key = cache_key(adapter_instance)
255
+ cached_hash =
256
+ cached_attributes.fetch(key) do
257
+ fetch(adapter_instance, serializer_class._cache_options, key) do
258
+ hash = attributes(cached_fields, true)
259
+ include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
260
+ hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
261
+ end
262
+ end
276
263
  # Merge both results
277
264
  adapter_instance.fragment_cache(cached_hash, non_cached_hash)
278
265
  end
279
266
 
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
267
  def cache_key(adapter_instance)
309
268
  return @cache_key if defined?(@cache_key)
310
269
 
311
270
  parts = []
312
271
  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('/')
272
+ parts << adapter_instance.cache_key
273
+ parts << serializer_class._cache_digest unless serializer_class._skip_digest?
274
+ @cache_key = expand_cache_key(parts)
275
+ end
276
+
277
+ def expand_cache_key(parts)
278
+ ActiveSupport::Cache.expand_cache_key(parts)
316
279
  end
317
280
 
318
281
  # Use object's cache_key if available, else derive a key from the object
@@ -320,14 +283,18 @@ module ActiveModel
320
283
  def object_cache_key
321
284
  if object.respond_to?(:cache_key)
322
285
  object.cache_key
323
- elsif (serializer_cache_key = (self.class._cache_key || self.class._cache_options[:key]))
286
+ elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key]))
324
287
  object_time_safe = object.updated_at
325
288
  object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
326
289
  "#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
327
290
  else
328
- fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{self.class}.cache'"
291
+ fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{serializer_class}.cache'"
329
292
  end
330
293
  end
294
+
295
+ def serializer_class
296
+ @serializer_class ||= self.class
297
+ end
331
298
  end
332
299
  end
333
300
  end
@@ -1,10 +1,14 @@
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
+ module ActiveModel
2
+ class Serializer
3
+ class ErrorSerializer < ActiveModel::Serializer
4
+ # @return [Hash<field_name,Array<error_message>>]
5
+ def as_json
6
+ object.errors.messages
7
+ end
6
8
 
7
- def success?
8
- false
9
+ def success?
10
+ false
11
+ end
12
+ end
9
13
  end
10
14
  end
@@ -1,27 +1,32 @@
1
1
  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
2
 
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
3
+ module ActiveModel
4
+ class Serializer
5
+ class ErrorsSerializer
6
+ include Enumerable
7
+ delegate :each, to: :@serializers
8
+ attr_reader :object, :root
15
9
 
16
- def success?
17
- false
18
- end
10
+ def initialize(resources, options = {})
11
+ @root = options[:root]
12
+ @object = resources
13
+ @serializers = resources.map do |resource|
14
+ serializer_class = options.fetch(:serializer) { ActiveModel::Serializer::ErrorSerializer }
15
+ serializer_class.new(resource, options.except(:serializer))
16
+ end
17
+ end
19
18
 
20
- def json_key
21
- nil
22
- end
19
+ def success?
20
+ false
21
+ end
23
22
 
24
- protected
23
+ def json_key
24
+ nil
25
+ end
25
26
 
26
- attr_reader :serializers
27
+ protected
28
+
29
+ attr_reader :serializers
30
+ end
31
+ end
27
32
  end
@@ -1,9 +1,9 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
3
  # @api private
4
- class HasManyReflection < CollectionReflection
5
- def macro
6
- :has_many
4
+ class HasManyReflection < Reflection
5
+ def collection?
6
+ true
7
7
  end
8
8
  end
9
9
  end
@@ -1,10 +1,7 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
3
  # @api private
4
- class HasOneReflection < SingularReflection
5
- def macro
6
- :has_one
7
- end
4
+ class HasOneReflection < Reflection
8
5
  end
9
6
  end
10
7
  end
@@ -0,0 +1,95 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ # @api private
4
+ LazyAssociation = Struct.new(:reflection, :association_options) do
5
+ REFLECTION_OPTIONS = %i(key links polymorphic meta serializer virtual_value namespace).freeze
6
+
7
+ delegate :collection?, to: :reflection
8
+
9
+ def reflection_options
10
+ @reflection_options ||= reflection.options.dup.reject { |k, _| !REFLECTION_OPTIONS.include?(k) }
11
+ end
12
+
13
+ def object
14
+ @object ||= reflection.value(
15
+ association_options.fetch(:parent_serializer),
16
+ association_options.fetch(:include_slice)
17
+ )
18
+ end
19
+ alias_method :eval_reflection_block, :object
20
+
21
+ def include_data?
22
+ eval_reflection_block if reflection.block
23
+ reflection.include_data?(
24
+ association_options.fetch(:include_slice)
25
+ )
26
+ end
27
+
28
+ # @return [ActiveModel::Serializer, nil]
29
+ def serializer
30
+ return @serializer if defined?(@serializer)
31
+ if serializer_class
32
+ serialize_object!(object)
33
+ elsif !object.nil? && !object.instance_of?(Object)
34
+ cached_result[:virtual_value] = object
35
+ end
36
+ @serializer = cached_result[:serializer]
37
+ end
38
+
39
+ def virtual_value
40
+ cached_result[:virtual_value] || reflection_options[:virtual_value]
41
+ end
42
+
43
+ def serializer_class
44
+ return @serializer_class if defined?(@serializer_class)
45
+ serializer_for_options = { namespace: namespace }
46
+ serializer_for_options[:serializer] = reflection_options[:serializer] if reflection_options.key?(:serializer)
47
+ @serializer_class = association_options.fetch(:parent_serializer).class.serializer_for(object, serializer_for_options)
48
+ end
49
+
50
+ private
51
+
52
+ def cached_result
53
+ @cached_result ||= {}
54
+ end
55
+
56
+ def serialize_object!(object)
57
+ if collection?
58
+ if (serializer = instantiate_collection_serializer(object)).nil?
59
+ # BUG: per #2027, JSON API resource relationships are only id and type, and hence either
60
+ # *require* a serializer or we need to be a little clever about figuring out the id/type.
61
+ # In either case, returning the raw virtual value will almost always be incorrect.
62
+ #
63
+ # Should be reflection_options[:virtual_value] or adapter needs to figure out what to do
64
+ # with an object that is non-nil and has no defined serializer.
65
+ cached_result[:virtual_value] = object.try(:as_json) || object
66
+ else
67
+ cached_result[:serializer] = serializer
68
+ end
69
+ else
70
+ cached_result[:serializer] = instantiate_serializer(object)
71
+ end
72
+ end
73
+
74
+ def instantiate_serializer(object)
75
+ serializer_options = association_options.fetch(:parent_serializer_options).except(:serializer)
76
+ serializer_options[:serializer_context_class] = association_options.fetch(:parent_serializer).class
77
+ serializer = reflection_options.fetch(:serializer, nil)
78
+ serializer_options[:serializer] = serializer if serializer
79
+ serializer_class.new(object, serializer_options)
80
+ end
81
+
82
+ def instantiate_collection_serializer(object)
83
+ serializer = catch(:no_serializer) do
84
+ instantiate_serializer(object)
85
+ end
86
+ serializer
87
+ end
88
+
89
+ def namespace
90
+ reflection_options[:namespace] ||
91
+ association_options.fetch(:parent_serializer_options)[:namespace]
92
+ end
93
+ end
94
+ end
95
+ end