active_model_serializers 0.8.0 → 0.10.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +702 -6
  3. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  4. data/README.md +195 -536
  5. data/lib/action_controller/serialization.rb +53 -35
  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 +73 -0
  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 +90 -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 +7 -0
  31. data/lib/active_model/serializer.rb +352 -441
  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 +90 -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/caching.rb +25 -0
  53. data/lib/active_model_serializers/model.rb +132 -0
  54. data/lib/active_model_serializers/railtie.rb +52 -0
  55. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  56. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  57. data/lib/active_model_serializers/serialization_context.rb +41 -0
  58. data/lib/active_model_serializers/test/schema.rb +140 -0
  59. data/lib/active_model_serializers/test/serializer.rb +127 -0
  60. data/lib/active_model_serializers/test.rb +9 -0
  61. data/lib/active_model_serializers.rb +49 -83
  62. data/lib/generators/rails/USAGE +6 -0
  63. data/lib/generators/rails/resource_override.rb +12 -0
  64. data/lib/generators/rails/serializer_generator.rb +38 -0
  65. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  66. data/lib/grape/active_model_serializers.rb +18 -0
  67. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  68. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  69. data/lib/tasks/rubocop.rake +55 -0
  70. metadata +291 -77
  71. data/.gitignore +0 -18
  72. data/.travis.yml +0 -29
  73. data/DESIGN.textile +0 -586
  74. data/Gemfile +0 -6
  75. data/Gemfile.edge +0 -9
  76. data/Rakefile +0 -18
  77. data/active_model_serializers.gemspec +0 -25
  78. data/bench/perf.rb +0 -43
  79. data/cruft.md +0 -19
  80. data/lib/active_model/array_serializer.rb +0 -104
  81. data/lib/active_model/serializer/associations.rb +0 -233
  82. data/lib/active_model/serializers/version.rb +0 -5
  83. data/lib/active_record/serializer_override.rb +0 -16
  84. data/lib/generators/resource_override.rb +0 -13
  85. data/lib/generators/serializer/USAGE +0 -9
  86. data/lib/generators/serializer/serializer_generator.rb +0 -42
  87. data/lib/generators/serializer/templates/serializer.rb +0 -19
  88. data/test/array_serializer_test.rb +0 -54
  89. data/test/association_test.rb +0 -592
  90. data/test/caching_test.rb +0 -96
  91. data/test/generators_test.rb +0 -85
  92. data/test/no_serialization_scope_test.rb +0 -34
  93. data/test/serialization_scope_name_test.rb +0 -67
  94. data/test/serialization_test.rb +0 -394
  95. data/test/serializer_support_test.rb +0 -51
  96. data/test/serializer_test.rb +0 -1452
  97. data/test/test_fakes.rb +0 -194
  98. data/test/test_helper.rb +0 -41
@@ -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