active_model_serializers 0.9.12 → 0.10.15

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +638 -82
  3. data/MIT-LICENSE +3 -2
  4. data/README.md +194 -846
  5. data/lib/action_controller/serialization.rb +34 -74
  6. data/lib/active_model/serializable_resource.rb +13 -0
  7. data/lib/active_model/serializer/adapter/attributes.rb +17 -0
  8. data/lib/active_model/serializer/adapter/base.rb +20 -0
  9. data/lib/active_model/serializer/adapter/json.rb +17 -0
  10. data/lib/active_model/serializer/adapter/json_api.rb +17 -0
  11. data/lib/active_model/serializer/adapter/null.rb +17 -0
  12. data/lib/active_model/serializer/adapter.rb +26 -0
  13. data/lib/active_model/serializer/array_serializer.rb +14 -0
  14. data/lib/active_model/serializer/association.rb +53 -38
  15. data/lib/active_model/serializer/attribute.rb +27 -0
  16. data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
  17. data/lib/active_model/serializer/collection_serializer.rb +99 -0
  18. data/lib/active_model/serializer/concerns/caching.rb +305 -0
  19. data/lib/active_model/serializer/error_serializer.rb +16 -0
  20. data/lib/active_model/serializer/errors_serializer.rb +34 -0
  21. data/lib/active_model/serializer/field.rb +92 -0
  22. data/lib/active_model/serializer/fieldset.rb +33 -0
  23. data/lib/active_model/serializer/has_many_reflection.rb +12 -0
  24. data/lib/active_model/serializer/has_one_reflection.rb +9 -0
  25. data/lib/active_model/serializer/lazy_association.rb +99 -0
  26. data/lib/active_model/serializer/link.rb +23 -0
  27. data/lib/active_model/serializer/lint.rb +152 -0
  28. data/lib/active_model/serializer/null.rb +19 -0
  29. data/lib/active_model/serializer/reflection.rb +212 -0
  30. data/lib/active_model/serializer/version.rb +1 -1
  31. data/lib/active_model/serializer.rb +361 -263
  32. data/lib/active_model_serializers/adapter/attributes.rb +36 -0
  33. data/lib/active_model_serializers/adapter/base.rb +85 -0
  34. data/lib/active_model_serializers/adapter/json.rb +23 -0
  35. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
  36. data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
  37. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
  38. data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
  39. data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
  40. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +94 -0
  41. data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
  42. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
  43. data/lib/active_model_serializers/adapter/json_api.rb +535 -0
  44. data/lib/active_model_serializers/adapter/null.rb +11 -0
  45. data/lib/active_model_serializers/adapter.rb +100 -0
  46. data/lib/active_model_serializers/callbacks.rb +57 -0
  47. data/lib/active_model_serializers/deprecate.rb +56 -0
  48. data/lib/active_model_serializers/deserialization.rb +17 -0
  49. data/lib/active_model_serializers/json_pointer.rb +16 -0
  50. data/lib/active_model_serializers/logging.rb +124 -0
  51. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  52. data/lib/active_model_serializers/model.rb +132 -0
  53. data/lib/active_model_serializers/railtie.rb +62 -0
  54. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  55. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  56. data/lib/active_model_serializers/serialization_context.rb +41 -0
  57. data/lib/active_model_serializers/test/schema.rb +140 -0
  58. data/lib/active_model_serializers/test/serializer.rb +127 -0
  59. data/lib/active_model_serializers/test.rb +9 -0
  60. data/lib/active_model_serializers.rb +58 -27
  61. data/lib/generators/rails/USAGE +6 -0
  62. data/lib/{active_model/serializer/generators → generators/rails}/resource_override.rb +1 -4
  63. data/lib/{active_model/serializer/generators/serializer → generators/rails}/serializer_generator.rb +4 -5
  64. data/lib/grape/active_model_serializers.rb +18 -0
  65. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  66. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  67. data/lib/tasks/rubocop.rake +60 -0
  68. metadata +248 -155
  69. data/CONTRIBUTING.md +0 -20
  70. data/DESIGN.textile +0 -586
  71. data/lib/action_controller/serialization_test_case.rb +0 -82
  72. data/lib/active_model/array_serializer.rb +0 -70
  73. data/lib/active_model/default_serializer.rb +0 -30
  74. data/lib/active_model/serializable/utils.rb +0 -18
  75. data/lib/active_model/serializable.rb +0 -61
  76. data/lib/active_model/serializer/association/has_many.rb +0 -41
  77. data/lib/active_model/serializer/association/has_one.rb +0 -27
  78. data/lib/active_model/serializer/config.rb +0 -33
  79. data/lib/active_model/serializer/generators/serializer/USAGE +0 -9
  80. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +0 -16
  81. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +0 -93
  82. data/lib/active_model/serializer/railtie.rb +0 -24
  83. data/lib/active_model/serializer_support.rb +0 -7
  84. data/test/benchmark/app.rb +0 -60
  85. data/test/benchmark/benchmarking_support.rb +0 -67
  86. data/test/benchmark/bm_active_record.rb +0 -41
  87. data/test/benchmark/setup.rb +0 -75
  88. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  89. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  90. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  91. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  92. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  93. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  94. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  95. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  96. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  97. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  98. data/test/fixtures/active_record.rb +0 -96
  99. data/test/fixtures/poro.rb +0 -255
  100. data/test/fixtures/template.html.erb +0 -1
  101. data/test/integration/action_controller/namespaced_serialization_test.rb +0 -105
  102. data/test/integration/action_controller/serialization_test.rb +0 -287
  103. data/test/integration/action_controller/serialization_test_case_test.rb +0 -71
  104. data/test/integration/active_record/active_record_test.rb +0 -94
  105. data/test/integration/generators/resource_generator_test.rb +0 -26
  106. data/test/integration/generators/scaffold_controller_generator_test.rb +0 -64
  107. data/test/integration/generators/serializer_generator_test.rb +0 -41
  108. data/test/test_app.rb +0 -18
  109. data/test/test_helper.rb +0 -31
  110. data/test/tmp/app/assets/javascripts/accounts.js +0 -2
  111. data/test/tmp/app/assets/stylesheets/accounts.css +0 -4
  112. data/test/tmp/app/controllers/accounts_controller.rb +0 -3
  113. data/test/tmp/app/helpers/accounts_helper.rb +0 -3
  114. data/test/tmp/app/serializers/account_serializer.rb +0 -4
  115. data/test/tmp/config/routes.rb +0 -2
  116. data/test/unit/active_model/array_serializer/except_test.rb +0 -18
  117. data/test/unit/active_model/array_serializer/key_format_test.rb +0 -18
  118. data/test/unit/active_model/array_serializer/meta_test.rb +0 -53
  119. data/test/unit/active_model/array_serializer/only_test.rb +0 -18
  120. data/test/unit/active_model/array_serializer/options_test.rb +0 -16
  121. data/test/unit/active_model/array_serializer/root_test.rb +0 -102
  122. data/test/unit/active_model/array_serializer/scope_test.rb +0 -24
  123. data/test/unit/active_model/array_serializer/serialization_test.rb +0 -239
  124. data/test/unit/active_model/default_serializer_test.rb +0 -13
  125. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +0 -36
  126. data/test/unit/active_model/serializer/associations_test.rb +0 -49
  127. data/test/unit/active_model/serializer/attributes_test.rb +0 -57
  128. data/test/unit/active_model/serializer/config_test.rb +0 -91
  129. data/test/unit/active_model/serializer/filter_test.rb +0 -69
  130. data/test/unit/active_model/serializer/has_many_polymorphic_test.rb +0 -189
  131. data/test/unit/active_model/serializer/has_many_test.rb +0 -265
  132. data/test/unit/active_model/serializer/has_one_and_has_many_test.rb +0 -27
  133. data/test/unit/active_model/serializer/has_one_polymorphic_test.rb +0 -196
  134. data/test/unit/active_model/serializer/has_one_test.rb +0 -253
  135. data/test/unit/active_model/serializer/key_format_test.rb +0 -25
  136. data/test/unit/active_model/serializer/meta_test.rb +0 -39
  137. data/test/unit/active_model/serializer/options_test.rb +0 -42
  138. data/test/unit/active_model/serializer/root_test.rb +0 -117
  139. data/test/unit/active_model/serializer/scope_test.rb +0 -49
  140. data/test/unit/active_model/serializer/url_helpers_test.rb +0 -36
  141. data/test/unit/active_model/serilizable_test.rb +0 -50
  142. /data/lib/{active_model/serializer/generators/serializer/templates/serializer.rb → generators/rails/templates/serializer.rb.erb} +0 -0
@@ -0,0 +1,305 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ UndefinedCacheKey = Class.new(StandardError)
6
+ module Caching
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ with_options instance_writer: false, instance_reader: false do |serializer|
11
+ serializer.class_attribute :_cache # @api private : the cache store
12
+ serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key. Ignored if the serializable object defines #cache_key.
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
15
+ serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
16
+ # _cache_options include:
17
+ # expires_in
18
+ # compress
19
+ # force
20
+ # race_condition_ttl
21
+ # Passed to ::_cache as
22
+ # serializer.cache_store.fetch(cache_key, @klass._cache_options)
23
+ # Passed as second argument to serializer.cache_store.fetch(cache_key, serializer_class._cache_options)
24
+ serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
25
+ end
26
+ end
27
+
28
+ # Matches
29
+ # "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
30
+ # AND
31
+ # "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
32
+ # AS
33
+ # c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
34
+ CALLER_FILE = /
35
+ \A # start of string
36
+ .+ # file path (one or more characters)
37
+ (?= # stop previous match when
38
+ :\d+ # a colon is followed by one or more digits
39
+ :in # followed by a colon followed by in
40
+ )
41
+ /x
42
+
43
+ module ClassMethods
44
+ def inherited(base)
45
+ caller_line = caller[1]
46
+ base._cache_digest_file_path = caller_line
47
+ super
48
+ end
49
+
50
+ def _cache_digest
51
+ return @_cache_digest if defined?(@_cache_digest)
52
+ @_cache_digest = digest_caller_file(_cache_digest_file_path)
53
+ end
54
+
55
+ # Hashes contents of file for +_cache_digest+
56
+ def digest_caller_file(caller_line)
57
+ serializer_file_path = caller_line[CALLER_FILE]
58
+ serializer_file_contents = IO.read(serializer_file_path)
59
+ algorithm = ActiveModelSerializers.config.use_sha1_digests ? Digest::SHA1 : Digest::MD5
60
+ algorithm.hexdigest(serializer_file_contents)
61
+ rescue TypeError, Errno::ENOENT
62
+ warn <<-EOF.strip_heredoc
63
+ Cannot digest non-existent file: '#{caller_line}'.
64
+ Please set `::_cache_digest` of the serializer
65
+ if you'd like to cache it.
66
+ EOF
67
+ ''.freeze
68
+ end
69
+
70
+ def _skip_digest?
71
+ _cache_options && _cache_options[:skip_digest]
72
+ end
73
+
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
84
+ end
85
+
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
+ }
95
+ end
96
+
97
+ # Enables a serializer to be automatically cached
98
+ #
99
+ # Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
100
+ # when Rails.configuration.action_controller.perform_caching
101
+ #
102
+ # @param options [Hash] with valid keys:
103
+ # cache_store : @see ::_cache
104
+ # key : @see ::_cache_key
105
+ # only : @see ::_cache_only
106
+ # except : @see ::_cache_except
107
+ # skip_digest : does not include digest in cache_key
108
+ # all else : @see ::_cache_options
109
+ #
110
+ # @example
111
+ # class PostSerializer < ActiveModel::Serializer
112
+ # cache key: 'post', expires_in: 3.hours
113
+ # attributes :title, :body
114
+ #
115
+ # has_many :comments
116
+ # end
117
+ #
118
+ # @todo require less code comments. See
119
+ # https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
120
+ def cache(options = {})
121
+ self._cache =
122
+ options.delete(:cache_store) ||
123
+ ActiveModelSerializers.config.cache_store ||
124
+ ActiveSupport::Cache.lookup_store(:null_store)
125
+ self._cache_key = options.delete(:key)
126
+ self._cache_only = options.delete(:only)
127
+ self._cache_except = options.delete(:except)
128
+ self._cache_options = options.empty? ? nil : options
129
+ end
130
+
131
+ # Value is from ActiveModelSerializers.config.perform_caching. Is used to
132
+ # globally enable or disable all serializer caching, just like
133
+ # Rails.configuration.action_controller.perform_caching, which is its
134
+ # default value in a Rails application.
135
+ # @return [true, false]
136
+ # Memoizes value of config first time it is called with a non-nil value.
137
+ # rubocop:disable Style/ClassVars
138
+ def perform_caching
139
+ return @@perform_caching if defined?(@@perform_caching) && !@@perform_caching.nil?
140
+ @@perform_caching = ActiveModelSerializers.config.perform_caching
141
+ end
142
+ alias perform_caching? perform_caching
143
+ # rubocop:enable Style/ClassVars
144
+
145
+ # The canonical method for getting the cache store for the serializer.
146
+ #
147
+ # @return [nil] when _cache is not set (i.e. when `cache` has not been called)
148
+ # @return [._cache] when _cache is not the NullStore
149
+ # @return [ActiveModelSerializers.config.cache_store] when _cache is the NullStore.
150
+ # This is so we can use `cache` being called to mean the serializer should be cached
151
+ # even if ActiveModelSerializers.config.cache_store has not yet been set.
152
+ # That means that when _cache is the NullStore and ActiveModelSerializers.config.cache_store
153
+ # is configured, `cache_store` becomes `ActiveModelSerializers.config.cache_store`.
154
+ # @return [nil] when _cache is the NullStore and ActiveModelSerializers.config.cache_store is nil.
155
+ def cache_store
156
+ return nil if _cache.nil?
157
+ return _cache if _cache.class != ActiveSupport::Cache::NullStore
158
+ if ActiveModelSerializers.config.cache_store
159
+ self._cache = ActiveModelSerializers.config.cache_store
160
+ else
161
+ nil
162
+ end
163
+ end
164
+
165
+ def cache_enabled?
166
+ perform_caching? && cache_store && !_cache_only && !_cache_except
167
+ end
168
+
169
+ def fragment_cache_enabled?
170
+ perform_caching? && cache_store &&
171
+ (_cache_only && !_cache_except || !_cache_only && _cache_except)
172
+ end
173
+
174
+ # Read cache from cache_store
175
+ # @return [Hash]
176
+ # Used in CollectionSerializer to set :cached_attributes
177
+ def cache_read_multi(collection_serializer, adapter_instance, include_directive)
178
+ return {} if ActiveModelSerializers.config.cache_store.blank?
179
+
180
+ keys = object_cache_keys(collection_serializer, adapter_instance, include_directive)
181
+
182
+ return {} if keys.blank?
183
+
184
+ ActiveModelSerializers.config.cache_store.read_multi(*keys)
185
+ end
186
+
187
+ # Find all cache_key for the collection_serializer
188
+ # @param serializers [ActiveModel::Serializer::CollectionSerializer]
189
+ # @param adapter_instance [ActiveModelSerializers::Adapter::Base]
190
+ # @param include_directive [JSONAPI::IncludeDirective]
191
+ # @return [Array] all cache_key of collection_serializer
192
+ def object_cache_keys(collection_serializer, adapter_instance, include_directive)
193
+ cache_keys = []
194
+
195
+ collection_serializer.each do |serializer|
196
+ cache_keys << object_cache_key(serializer, adapter_instance)
197
+
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|
203
+ cache_keys << object_cache_key(sub_serializer, adapter_instance)
204
+ end
205
+ else
206
+ cache_keys << object_cache_key(association_serializer, adapter_instance)
207
+ end
208
+ end
209
+ end
210
+
211
+ cache_keys.compact.uniq
212
+ end
213
+
214
+ # @return [String, nil] the cache_key of the serializer or nil
215
+ def object_cache_key(serializer, adapter_instance)
216
+ return unless serializer.present? && serializer.object.present?
217
+
218
+ (serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil
219
+ end
220
+ end
221
+
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
229
+ end
230
+ end
231
+
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
236
+ yield
237
+ end
238
+ else
239
+ yield
240
+ end
241
+ end
242
+
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
266
+ # Merge both results
267
+ adapter_instance.fragment_cache(cached_hash, non_cached_hash)
268
+ end
269
+
270
+ def cache_key(adapter_instance)
271
+ return @cache_key if defined?(@cache_key)
272
+
273
+ parts = []
274
+ parts << object_cache_key
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)
282
+ end
283
+
284
+ # Use object's cache_key if available, else derive a key from the object
285
+ # Pass the `key` option to the `cache` declaration or override this method to customize the cache key
286
+ def object_cache_key
287
+ if object.respond_to?(:cache_key_with_version)
288
+ object.cache_key_with_version
289
+ elsif object.respond_to?(:cache_key)
290
+ object.cache_key
291
+ elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key]))
292
+ object_time_safe = object.updated_at
293
+ object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
294
+ "#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
295
+ else
296
+ fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{serializer_class}.cache'"
297
+ end
298
+ end
299
+
300
+ def serializer_class
301
+ @serializer_class ||= self.class
302
+ end
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,16 @@
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
10
+
11
+ def success?
12
+ false
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/serializer/error_serializer'
4
+
5
+ module ActiveModel
6
+ class Serializer
7
+ class ErrorsSerializer
8
+ include Enumerable
9
+ delegate :each, to: :@serializers
10
+ attr_reader :object, :root
11
+
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
20
+
21
+ def success?
22
+ false
23
+ end
24
+
25
+ def json_key
26
+ nil
27
+ end
28
+
29
+ protected
30
+
31
+ attr_reader :serializers
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ # Holds all the meta-data about a field (i.e. attribute or association) as it was
6
+ # specified in the ActiveModel::Serializer class.
7
+ # Notice that the field block is evaluated in the context of the serializer.
8
+ Field = Struct.new(:name, :options, :block) do
9
+ def initialize(*)
10
+ super
11
+
12
+ validate_condition!
13
+ end
14
+
15
+ # Compute the actual value of a field for a given serializer instance.
16
+ # @param [Serializer] The serializer instance for which the value is computed.
17
+ # @return [Object] value
18
+ #
19
+ # @api private
20
+ #
21
+ def value(serializer)
22
+ if block
23
+ serializer.instance_eval(&block)
24
+ else
25
+ serializer.read_attribute_for_serialization(name)
26
+ end
27
+ end
28
+
29
+ # Decide whether the field should be serialized by the given serializer instance.
30
+ # @param [Serializer] The serializer instance
31
+ # @return [Bool]
32
+ #
33
+ # @api private
34
+ #
35
+ def excluded?(serializer)
36
+ case condition_type
37
+ when :if
38
+ !evaluate_condition(serializer)
39
+ when :unless
40
+ evaluate_condition(serializer)
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def validate_condition!
49
+ return if condition_type == :none
50
+
51
+ case condition
52
+ when Symbol, String, Proc
53
+ # noop
54
+ else
55
+ fail TypeError, "#{condition_type.inspect} should be a Symbol, String or Proc"
56
+ end
57
+ end
58
+
59
+ def evaluate_condition(serializer)
60
+ case condition
61
+ when Symbol
62
+ serializer.public_send(condition)
63
+ when String
64
+ serializer.instance_eval(condition)
65
+ when Proc
66
+ if condition.arity.zero?
67
+ serializer.instance_exec(&condition)
68
+ else
69
+ serializer.instance_exec(serializer, &condition)
70
+ end
71
+ else
72
+ nil
73
+ end
74
+ end
75
+
76
+ def condition_type
77
+ @condition_type ||=
78
+ if options.key?(:if)
79
+ :if
80
+ elsif options.key?(:unless)
81
+ :unless
82
+ else
83
+ :none
84
+ end
85
+ end
86
+
87
+ def condition
88
+ options[condition_type]
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class Fieldset
6
+ def initialize(fields)
7
+ @raw_fields = fields || {}
8
+ end
9
+
10
+ def fields
11
+ @fields ||= parsed_fields
12
+ end
13
+
14
+ def fields_for(type)
15
+ fields[type.to_s.singularize.to_sym] || fields[type.to_s.pluralize.to_sym]
16
+ end
17
+
18
+ protected
19
+
20
+ attr_reader :raw_fields
21
+
22
+ private
23
+
24
+ def parsed_fields
25
+ if raw_fields.is_a?(Hash)
26
+ raw_fields.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.map(&:to_sym) }
27
+ else
28
+ {}
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ # @api private
6
+ class HasManyReflection < Reflection
7
+ def collection?
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ # @api private
6
+ class HasOneReflection < Reflection
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ # @api private
6
+ LazyAssociation = Struct.new(:reflection, :association_options) do
7
+ REFLECTION_OPTIONS = %i(key links polymorphic meta serializer virtual_value namespace).freeze
8
+
9
+ delegate :collection?, to: :reflection
10
+
11
+ def reflection_options
12
+ @reflection_options ||= reflection.options.select { |k, _| REFLECTION_OPTIONS.include?(k) }
13
+ end
14
+
15
+ def object
16
+ return @object if defined?(@object)
17
+ @object = reflection.value(
18
+ association_options.fetch(:parent_serializer),
19
+ association_options.fetch(:include_slice)
20
+ )
21
+ end
22
+ alias_method :eval_reflection_block, :object
23
+
24
+ def include_data?
25
+ eval_reflection_block if reflection.block
26
+ reflection.include_data?(
27
+ association_options.fetch(:include_slice)
28
+ )
29
+ end
30
+
31
+ # @return [ActiveModel::Serializer, nil]
32
+ def serializer
33
+ return @serializer if defined?(@serializer)
34
+ if serializer_class
35
+ serialize_object!(object)
36
+ elsif !object.nil? && !object.instance_of?(Object)
37
+ cached_result[:virtual_value] = object
38
+ end
39
+ @serializer = cached_result[:serializer]
40
+ end
41
+
42
+ def virtual_value
43
+ cached_result[:virtual_value] || reflection_options[:virtual_value]
44
+ end
45
+
46
+ def serializer_class
47
+ return @serializer_class if defined?(@serializer_class)
48
+ serializer_for_options = { namespace: namespace }
49
+ serializer_for_options[:serializer] = reflection_options[:serializer] if reflection_options.key?(:serializer)
50
+ @serializer_class = association_options.fetch(:parent_serializer).class.serializer_for(object, serializer_for_options)
51
+ end
52
+
53
+ private
54
+
55
+ def cached_result
56
+ @cached_result ||= {}
57
+ end
58
+
59
+ def serialize_object!(object)
60
+ if collection?
61
+ if (serializer = instantiate_collection_serializer(object)).nil?
62
+ # BUG: per #2027, JSON API resource relationships are only id and type, and hence either
63
+ # *require* a serializer or we need to be a little clever about figuring out the id/type.
64
+ # In either case, returning the raw virtual value will almost always be incorrect.
65
+ #
66
+ # Should be reflection_options[:virtual_value] or adapter needs to figure out what to do
67
+ # with an object that is non-nil and has no defined serializer.
68
+ cached_result[:virtual_value] = object.try(:as_json) || object
69
+ else
70
+ cached_result[:serializer] = serializer
71
+ end
72
+ else
73
+ cached_result[:serializer] = instantiate_serializer(object)
74
+ end
75
+ end
76
+
77
+ def instantiate_serializer(object)
78
+ serializer_options = association_options.fetch(:parent_serializer_options).except(:serializer)
79
+ serializer_options[:serializer_context_class] = association_options.fetch(:parent_serializer).class
80
+ serializer = reflection_options.fetch(:serializer, nil)
81
+ serializer_options[:serializer] = serializer if serializer
82
+ serializer_options[:namespace] = reflection_options[:namespace] if reflection_options[:namespace]
83
+ serializer_class.new(object, serializer_options)
84
+ end
85
+
86
+ def instantiate_collection_serializer(object)
87
+ serializer = catch(:no_serializer) do
88
+ instantiate_serializer(object)
89
+ end
90
+ serializer
91
+ end
92
+
93
+ def namespace
94
+ reflection_options[:namespace] ||
95
+ association_options.fetch(:parent_serializer_options)[:namespace]
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/serializer/field'
4
+
5
+ module ActiveModel
6
+ class Serializer
7
+ # Holds all the data about a serializer link
8
+ #
9
+ # @example
10
+ # class PostSerializer < ActiveModel::Serializer
11
+ # link :callback, if: :internal? do
12
+ # object.callback_link
13
+ # end
14
+ #
15
+ # def internal?
16
+ # instance_options[:internal] == true
17
+ # end
18
+ # end
19
+ #
20
+ class Link < Field
21
+ end
22
+ end
23
+ end