active_model_serializers 0.8.3 → 0.10.0.rc2

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +18 -20
  4. data/CHANGELOG.md +8 -67
  5. data/CONTRIBUTING.md +31 -0
  6. data/Gemfile +14 -1
  7. data/{MIT-LICENSE.txt → LICENSE.txt} +3 -2
  8. data/README.md +169 -495
  9. data/Rakefile +6 -12
  10. data/active_model_serializers.gemspec +21 -19
  11. data/lib/action_controller/serialization.rb +36 -27
  12. data/lib/active_model/serializer/adapter/flatten_json.rb +12 -0
  13. data/lib/active_model/serializer/adapter/fragment_cache.rb +78 -0
  14. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +15 -0
  15. data/lib/active_model/serializer/adapter/json.rb +50 -0
  16. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +23 -0
  17. data/lib/active_model/serializer/adapter/json_api.rb +156 -0
  18. data/lib/active_model/serializer/adapter/null.rb +11 -0
  19. data/lib/active_model/serializer/adapter.rb +96 -0
  20. data/lib/active_model/serializer/array_serializer.rb +35 -0
  21. data/lib/active_model/serializer/configuration.rb +13 -0
  22. data/lib/active_model/serializer/fieldset.rb +40 -0
  23. data/lib/active_model/serializer/railtie.rb +8 -0
  24. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  25. data/lib/active_model/serializer.rb +177 -440
  26. data/lib/active_model_serializers.rb +10 -86
  27. data/lib/generators/serializer/USAGE +0 -3
  28. data/lib/generators/serializer/resource_override.rb +12 -0
  29. data/lib/generators/serializer/serializer_generator.rb +1 -6
  30. data/lib/generators/serializer/templates/serializer.rb +2 -13
  31. data/test/action_controller/adapter_selector_test.rb +53 -0
  32. data/test/action_controller/explicit_serializer_test.rb +134 -0
  33. data/test/action_controller/json_api_linked_test.rb +179 -0
  34. data/test/action_controller/rescue_from_test.rb +32 -0
  35. data/test/{serialization_scope_name_test.rb → action_controller/serialization_scope_name_test.rb} +7 -11
  36. data/test/action_controller/serialization_test.rb +383 -0
  37. data/test/adapter/fragment_cache_test.rb +27 -0
  38. data/test/adapter/json/belongs_to_test.rb +48 -0
  39. data/test/adapter/json/collection_test.rb +73 -0
  40. data/test/adapter/json/has_many_test.rb +36 -0
  41. data/test/adapter/json_api/belongs_to_test.rb +157 -0
  42. data/test/adapter/json_api/collection_test.rb +96 -0
  43. data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
  44. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
  45. data/test/adapter/json_api/has_many_test.rb +110 -0
  46. data/test/adapter/json_api/has_one_test.rb +61 -0
  47. data/test/adapter/json_api/linked_test.rb +283 -0
  48. data/test/adapter/json_test.rb +34 -0
  49. data/test/adapter/null_test.rb +25 -0
  50. data/test/adapter_test.rb +43 -0
  51. data/test/array_serializer_test.rb +31 -63
  52. data/test/fixtures/poro.rb +230 -0
  53. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  54. data/test/{generators_test.rb → generators/serializer_generator_test.rb} +2 -36
  55. data/test/serializers/adapter_for_test.rb +50 -0
  56. data/test/serializers/associations_test.rb +127 -0
  57. data/test/serializers/attribute_test.rb +38 -0
  58. data/test/serializers/attributes_test.rb +63 -0
  59. data/test/serializers/cache_test.rb +138 -0
  60. data/test/serializers/configuration_test.rb +15 -0
  61. data/test/serializers/fieldset_test.rb +26 -0
  62. data/test/serializers/meta_test.rb +107 -0
  63. data/test/serializers/options_test.rb +21 -0
  64. data/test/serializers/serializer_for_test.rb +65 -0
  65. data/test/serializers/urls_test.rb +26 -0
  66. data/test/test_helper.rb +28 -16
  67. metadata +109 -43
  68. data/DESIGN.textile +0 -586
  69. data/Gemfile.edge +0 -9
  70. data/bench/perf.rb +0 -43
  71. data/cruft.md +0 -19
  72. data/lib/active_model/array_serializer.rb +0 -104
  73. data/lib/active_model/serializer/associations.rb +0 -233
  74. data/lib/active_record/serializer_override.rb +0 -16
  75. data/lib/generators/resource_override.rb +0 -13
  76. data/test/association_test.rb +0 -592
  77. data/test/caching_test.rb +0 -96
  78. data/test/no_serialization_scope_test.rb +0 -34
  79. data/test/serialization_test.rb +0 -392
  80. data/test/serializer_support_test.rb +0 -51
  81. data/test/serializer_test.rb +0 -1465
  82. data/test/test_fakes.rb +0 -217
@@ -1,298 +1,165 @@
1
- require "active_support/core_ext/class/attribute"
2
- require "active_support/core_ext/module/anonymous"
3
- require 'active_support/dependencies'
4
- require 'active_support/descendants_tracker'
1
+ require 'thread_safe'
5
2
 
6
3
  module ActiveModel
7
- # Active Model Serializer
8
- #
9
- # Provides a basic serializer implementation that allows you to easily
10
- # control how a given object is going to be serialized. On initialization,
11
- # it expects two objects as arguments, a resource and options. For example,
12
- # one may do in a controller:
13
- #
14
- # PostSerializer.new(@post, :scope => current_user).to_json
15
- #
16
- # The object to be serialized is the +@post+ and the current user is passed
17
- # in for authorization purposes.
18
- #
19
- # We use the scope to check if a given attribute should be serialized or not.
20
- # For example, some attributes may only be returned if +current_user+ is the
21
- # author of the post:
22
- #
23
- # class PostSerializer < ActiveModel::Serializer
24
- # attributes :title, :body
25
- # has_many :comments
26
- #
27
- # private
28
- #
29
- # def attributes
30
- # hash = super
31
- # hash.merge!(:email => post.email) if author?
32
- # hash
33
- # end
34
- #
35
- # def author?
36
- # post.author == scope
37
- # end
38
- # end
39
- #
40
4
  class Serializer
41
- extend ActiveSupport::DescendantsTracker
5
+ extend ActiveSupport::Autoload
6
+ autoload :Configuration
7
+ autoload :ArraySerializer
8
+ autoload :Adapter
9
+ include Configuration
42
10
 
43
- INCLUDE_METHODS = {}
44
- INSTRUMENT = { :serialize => :"serialize.serializer", :associations => :"associations.serializer" }
11
+ class << self
12
+ attr_accessor :_attributes
13
+ attr_accessor :_attributes_keys
14
+ attr_accessor :_associations
15
+ attr_accessor :_urls
16
+ attr_accessor :_cache
17
+ attr_accessor :_fragmented
18
+ attr_accessor :_cache_key
19
+ attr_accessor :_cache_only
20
+ attr_accessor :_cache_except
21
+ attr_accessor :_cache_options
22
+ attr_accessor :_cache_digest
23
+ end
45
24
 
46
- class IncludeError < StandardError
47
- attr_reader :source, :association
25
+ def self.inherited(base)
26
+ base._attributes = self._attributes.try(:dup) || []
27
+ base._attributes_keys = self._attributes_keys.try(:dup) || {}
28
+ base._associations = self._associations.try(:dup) || {}
29
+ base._urls = []
30
+ serializer_file = File.open(caller.first[/^[^:]+/])
31
+ base._cache_digest = Digest::MD5.hexdigest(serializer_file.read)
32
+ end
48
33
 
49
- def initialize(source, association)
50
- @source, @association = source, association
51
- end
34
+ def self.attributes(*attrs)
35
+ attrs = attrs.first if attrs.first.class == Array
36
+ @_attributes.concat attrs
37
+ @_attributes.uniq!
52
38
 
53
- def to_s
54
- "Cannot serialize #{association} when #{source} does not have a root!"
39
+ attrs.each do |attr|
40
+ define_method attr do
41
+ object && object.read_attribute_for_serialization(attr)
42
+ end unless method_defined?(attr) || _fragmented.respond_to?(attr)
55
43
  end
56
44
  end
57
45
 
58
- class_attribute :_attributes
59
- self._attributes = {}
60
-
61
- class_attribute :_associations
62
- self._associations = {}
63
-
64
- class_attribute :_root
65
- class_attribute :_embed
66
- self._embed = :objects
67
- class_attribute :_root_embed
68
-
69
- class_attribute :cache
70
- class_attribute :perform_caching
46
+ def self.attribute(attr, options = {})
47
+ key = options.fetch(:key, attr)
48
+ @_attributes_keys[attr] = {key: key} if key != attr
49
+ @_attributes << key unless @_attributes.include?(key)
50
+ define_method key do
51
+ object.read_attribute_for_serialization(attr)
52
+ end unless method_defined?(key) || _fragmented.respond_to?(attr)
53
+ end
71
54
 
72
- class << self
73
- # set perform caching like root
74
- def cached(value = true)
75
- self.perform_caching = value
76
- end
55
+ def self.fragmented(serializer)
56
+ @_fragmented = serializer
57
+ end
77
58
 
78
- # Define attributes to be used in the serialization.
79
- def attributes(*attrs)
59
+ # Enables a serializer to be automatically cached
60
+ def self.cache(options = {})
61
+ @_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
62
+ @_cache_key = options.delete(:key)
63
+ @_cache_only = options.delete(:only)
64
+ @_cache_except = options.delete(:except)
65
+ @_cache_options = (options.empty?) ? nil : options
66
+ end
80
67
 
81
- self._attributes = _attributes.dup
68
+ # Defines an association in the object should be rendered.
69
+ #
70
+ # The serializer object should implement the association name
71
+ # as a method which should return an array when invoked. If a method
72
+ # with the association name does not exist, the association name is
73
+ # dispatched to the serialized object.
74
+ def self.has_many(*attrs)
75
+ associate(:has_many, attrs)
76
+ end
82
77
 
83
- attrs.each do |attr|
84
- if Hash === attr
85
- attr.each {|attr_real, key| attribute attr_real, :key => key }
86
- else
87
- attribute attr
88
- end
89
- end
90
- end
78
+ # Defines an association in the object that should be rendered.
79
+ #
80
+ # The serializer object should implement the association name
81
+ # as a method which should return an object when invoked. If a method
82
+ # with the association name does not exist, the association name is
83
+ # dispatched to the serialized object.
84
+ def self.belongs_to(*attrs)
85
+ associate(:belongs_to, attrs)
86
+ end
91
87
 
92
- def attribute(attr, options={})
93
- self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym})
88
+ # Defines an association in the object should be rendered.
89
+ #
90
+ # The serializer object should implement the association name
91
+ # as a method which should return an object when invoked. If a method
92
+ # with the association name does not exist, the association name is
93
+ # dispatched to the serialized object.
94
+ def self.has_one(*attrs)
95
+ associate(:has_one, attrs)
96
+ end
94
97
 
95
- attr = attr.keys[0] if attr.is_a? Hash
98
+ def self.associate(type, attrs) #:nodoc:
99
+ options = attrs.extract_options!
100
+ self._associations = _associations.dup
96
101
 
102
+ attrs.each do |attr|
97
103
  unless method_defined?(attr)
98
104
  define_method attr do
99
- object.read_attribute_for_serialization(attr.to_sym)
100
- end
101
- end
102
-
103
- define_include_method attr
104
-
105
- # protect inheritance chains and open classes
106
- # if a serializer inherits from another OR
107
- # attributes are added later in a classes lifecycle
108
- # poison the cache
109
- define_method :_fast_attributes do
110
- raise NameError
111
- end
112
-
113
- end
114
-
115
- def associate(klass, attrs) #:nodoc:
116
- options = attrs.extract_options!
117
- self._associations = _associations.dup
118
-
119
- attrs.each do |attr|
120
- unless method_defined?(attr)
121
- define_method attr do
122
- object.send attr
123
- end
124
- end
125
-
126
- define_include_method attr
127
-
128
- self._associations[attr] = klass.refine(attr, options)
129
- end
130
- end
131
-
132
- def define_include_method(name)
133
- method = "include_#{name}?".to_sym
134
-
135
- INCLUDE_METHODS[name] = method
136
-
137
- unless method_defined?(method)
138
- define_method method do
139
- true
105
+ object.send attr
140
106
  end
141
107
  end
142
- end
143
-
144
- # Defines an association in the object should be rendered.
145
- #
146
- # The serializer object should implement the association name
147
- # as a method which should return an array when invoked. If a method
148
- # with the association name does not exist, the association name is
149
- # dispatched to the serialized object.
150
- def has_many(*attrs)
151
- associate(Associations::HasMany, attrs)
152
- end
153
108
 
154
- # Defines an association in the object should be rendered.
155
- #
156
- # The serializer object should implement the association name
157
- # as a method which should return an object when invoked. If a method
158
- # with the association name does not exist, the association name is
159
- # dispatched to the serialized object.
160
- def has_one(*attrs)
161
- associate(Associations::HasOne, attrs)
109
+ self._associations[attr] = {type: type, association_options: options}
162
110
  end
111
+ end
163
112
 
164
- # Return a schema hash for the current serializer. This information
165
- # can be used to generate clients for the serialized output.
166
- #
167
- # The schema hash has two keys: +attributes+ and +associations+.
168
- #
169
- # The +attributes+ hash looks like this:
170
- #
171
- # { :name => :string, :age => :integer }
172
- #
173
- # The +associations+ hash looks like this:
174
- # { :posts => { :has_many => :posts } }
175
- #
176
- # If :key is used:
177
- #
178
- # class PostsSerializer < ActiveModel::Serializer
179
- # has_many :posts, :key => :my_posts
180
- # end
181
- #
182
- # the hash looks like this:
183
- #
184
- # { :my_posts => { :has_many => :posts }
185
- #
186
- # This information is extracted from the serializer's model class,
187
- # which is provided by +SerializerClass.model_class+.
188
- #
189
- # The schema method uses the +columns_hash+ and +reflect_on_association+
190
- # methods, provided by default by ActiveRecord. You can implement these
191
- # methods on your custom models if you want the serializer's schema method
192
- # to work.
193
- #
194
- # TODO: This is currently coupled to Active Record. We need to
195
- # figure out a way to decouple those two.
196
- def schema
197
- klass = model_class
198
- columns = klass.columns_hash
199
-
200
- attrs = {}
201
- _attributes.each do |name, key|
202
- if column = columns[name.to_s]
203
- attrs[key] = column.type
204
- else
205
- # Computed attribute (method on serializer or model). We cannot
206
- # infer the type, so we put nil, unless specified in the attribute declaration
207
- if name != key
208
- attrs[name] = key
209
- else
210
- attrs[key] = nil
211
- end
212
- end
213
- end
214
-
215
- associations = {}
216
- _associations.each do |attr, association_class|
217
- association = association_class.new(attr, self)
218
-
219
- if model_association = klass.reflect_on_association(association.name)
220
- # Real association.
221
- associations[association.key] = { model_association.macro => model_association.name }
222
- else
223
- # Computed association. We could infer has_many vs. has_one from
224
- # the association class, but that would make it different from
225
- # real associations, which read has_one vs. belongs_to from the
226
- # model.
227
- associations[association.key] = nil
228
- end
229
- end
113
+ def self.url(attr)
114
+ @_urls.push attr
115
+ end
230
116
 
231
- { :attributes => attrs, :associations => associations }
232
- end
117
+ def self.urls(*attrs)
118
+ @_urls.concat attrs
119
+ end
233
120
 
234
- # The model class associated with this serializer.
235
- def model_class
236
- name.sub(/Serializer$/, '').constantize
121
+ def self.serializer_for(resource, options = {})
122
+ if resource.respond_to?(:serializer_class)
123
+ resource.serializer_class
124
+ elsif resource.respond_to?(:to_ary)
125
+ config.array_serializer
126
+ else
127
+ options
128
+ .fetch(:association_options, {})
129
+ .fetch(:serializer, get_serializer_for(resource.class))
237
130
  end
131
+ end
238
132
 
239
- # Define how associations should be embedded.
240
- #
241
- # embed :objects # Embed associations as full objects
242
- # embed :ids # Embed only the association ids
243
- # embed :ids, :include => true # Embed the association ids and include objects in the root
244
- #
245
- def embed(type, options={})
246
- self._embed = type
247
- self._root_embed = true if options[:include]
133
+ def self.adapter
134
+ adapter_class = case config.adapter
135
+ when Symbol
136
+ ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
137
+ when Class
138
+ config.adapter
248
139
  end
249
-
250
- # Defines the root used on serialization. If false, disables the root.
251
- def root(name)
252
- self._root = name
140
+ unless adapter_class
141
+ valid_adapters = Adapter.constants.map { |klass| ":#{klass.to_s.downcase}" }
142
+ raise ArgumentError, "Unknown adapter: #{config.adapter}. Valid adapters are: #{valid_adapters}"
253
143
  end
254
- alias_method :root=, :root
255
-
256
- # Used internally to create a new serializer object based on controller
257
- # settings and options for a given resource. These settings are typically
258
- # set during the request lifecycle or by the controller class, and should
259
- # not be manually defined for this method.
260
- def build_json(controller, resource, options)
261
- default_options = controller.send(:default_serializer_options) || {}
262
- options = default_options.merge(options || {})
263
-
264
- serializer = options.delete(:serializer) ||
265
- (resource.respond_to?(:active_model_serializer) &&
266
- resource.active_model_serializer)
267
-
268
- return serializer unless serializer
269
-
270
- if resource.respond_to?(:to_ary)
271
- unless serializer <= ActiveModel::ArraySerializer
272
- raise ArgumentError.new("#{serializer.name} is not an ArraySerializer. " +
273
- "You may want to use the :each_serializer option instead.")
274
- end
275
144
 
276
- if options[:root] != false && serializer.root != false
277
- # the serializer for an Array is ActiveModel::ArraySerializer
278
- options[:root] ||= serializer.root || controller.controller_name
279
- end
280
- end
281
-
282
- options[:scope] = controller.serialization_scope unless options.has_key?(:scope)
283
- options[:scope_name] = controller._serialization_scope
284
- options[:url_options] = controller.url_options
145
+ adapter_class
146
+ end
285
147
 
286
- serializer.new(resource, options)
287
- end
148
+ def self.root_name
149
+ name.demodulize.underscore.sub(/_serializer$/, '') if name
288
150
  end
289
151
 
290
- attr_reader :object, :options
152
+ attr_accessor :object, :root, :meta, :meta_key, :scope
291
153
 
292
- def initialize(object, options={})
293
- @object, @options = object, options
154
+ def initialize(object, options = {})
155
+ @object = object
156
+ @options = options
157
+ @root = options[:root]
158
+ @meta = options[:meta]
159
+ @meta_key = options[:meta_key]
160
+ @scope = options[:scope]
294
161
 
295
- scope_name = @options[:scope_name]
162
+ scope_name = options[:scope_name]
296
163
  if scope_name && !respond_to?(scope_name)
297
164
  self.class.class_eval do
298
165
  define_method scope_name, lambda { scope }
@@ -300,216 +167,86 @@ module ActiveModel
300
167
  end
301
168
  end
302
169
 
303
- def root_name
304
- return false if self._root == false
305
-
306
- class_name = self.class.name.demodulize.underscore.sub(/_serializer$/, '').to_sym unless self.class.name.blank?
307
-
308
- if self._root == true
309
- class_name
310
- else
311
- self._root || class_name
312
- end
313
- end
314
-
315
- def url_options
316
- @options[:url_options] || {}
317
- end
318
-
319
- def meta_key
320
- @options[:meta_key].try(:to_sym) || :meta
321
- end
322
-
323
- def include_meta(hash)
324
- hash[meta_key] = @options[:meta] if @options.has_key?(:meta)
325
- end
326
-
327
- def to_json(*args)
328
- if perform_caching?
329
- cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) do
330
- super
331
- end
332
- else
333
- super
334
- end
335
- end
336
-
337
- # Returns a json representation of the serializable
338
- # object including the root.
339
- def as_json(options={})
340
- options ||= {}
341
- if root = options.fetch(:root, @options.fetch(:root, root_name))
342
- @options[:hash] = hash = {}
343
- @options[:unique_values] = {}
344
-
345
- hash.merge!(root => serializable_hash)
346
- include_meta hash
347
- hash
348
- else
349
- serializable_hash
350
- end
351
- end
352
-
353
- # Returns a hash representation of the serializable
354
- # object without the root.
355
- def serializable_hash
356
- if perform_caching?
357
- cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'serializable-hash']) do
358
- _serializable_hash
359
- end
360
- else
361
- _serializable_hash
362
- end
170
+ def json_key
171
+ self.class.root_name
363
172
  end
364
173
 
365
- def include_associations!
366
- _associations.each_key do |name|
367
- include!(name) if include?(name)
368
- end
174
+ def id
175
+ object.id if object
369
176
  end
370
177
 
371
- def include?(name)
372
- return false if @options.key?(:only) && !Array(@options[:only]).include?(name)
373
- return false if @options.key?(:except) && Array(@options[:except]).include?(name)
374
- send INCLUDE_METHODS[name]
178
+ def type
179
+ object.class.model_name.plural
375
180
  end
376
181
 
377
- def include!(name, options={})
378
- # Make sure that if a special options[:hash] was passed in, we generate
379
- # a new unique values hash and don't clobber the original. If the hash
380
- # passed in is the same as the current options hash, use the current
381
- # unique values.
382
- #
383
- # TODO: Should passing in a Hash even be public API here?
384
- unique_values =
385
- if hash = options[:hash]
386
- if @options[:hash] == hash
387
- @options[:unique_values] ||= {}
388
- else
389
- {}
390
- end
182
+ def attributes(options = {})
183
+ attributes =
184
+ if options[:fields]
185
+ self.class._attributes & options[:fields]
391
186
  else
392
- hash = @options[:hash]
393
- @options[:unique_values] ||= {}
187
+ self.class._attributes.dup
394
188
  end
395
189
 
396
- node = options[:node] ||= @node
397
- value = options[:value]
398
-
399
- if options[:include] == nil
400
- if @options.key?(:include)
401
- options[:include] = @options[:include].include?(name)
402
- elsif @options.include?(:exclude)
403
- options[:include] = !@options[:exclude].include?(name)
404
- end
405
- end
190
+ attributes += options[:required_fields] if options[:required_fields]
406
191
 
407
- association_class =
408
- if klass = _associations[name]
409
- klass
410
- elsif value.respond_to?(:to_ary)
411
- Associations::HasMany
192
+ attributes.each_with_object({}) do |name, hash|
193
+ unless self.class._fragmented
194
+ hash[name] = send(name)
412
195
  else
413
- Associations::HasOne
196
+ hash[name] = self.class._fragmented.public_send(name)
414
197
  end
415
-
416
- association = association_class.new(name, self, options)
417
-
418
- if association.embed_ids?
419
- node[association.key] = association.serialize_ids
420
-
421
- if association.embed_in_root? && hash.nil?
422
- raise IncludeError.new(self.class, association.name)
423
- elsif association.embed_in_root? && association.embeddable?
424
- merge_association hash, association.root, association.serializables, unique_values
425
- end
426
- elsif association.embed_objects?
427
- node[association.key] = association.serialize
428
198
  end
429
199
  end
430
200
 
431
- # In some cases, an Array of associations is built by merging the associated
432
- # content for all of the children. For instance, if a Post has_many comments,
433
- # which has_many tags, the top-level :tags key will contain the merged list
434
- # of all tags for all comments of the post.
435
- #
436
- # In order to make this efficient, we store a :unique_values hash containing
437
- # a unique list of all of the objects that are already in the Array. This
438
- # avoids the need to scan through the Array looking for entries every time
439
- # we want to merge a new list of values.
440
- def merge_association(hash, key, serializables, unique_values)
441
- already_serialized = (unique_values[key] ||= {})
442
- serializable_hashes = (hash[key] ||= [])
443
-
444
- serializables.each do |serializable|
445
- unless already_serialized.include? serializable.object
446
- already_serialized[serializable.object] = true
447
- serializable_hashes << serializable.serializable_hash
448
- end
449
- end
450
- end
451
-
452
- # Returns a hash representation of the serializable
453
- # object attributes.
454
- def attributes
455
- _fast_attributes
456
- rescue NameError
457
- method = "def _fast_attributes\n"
201
+ def each_association(&block)
202
+ self.class._associations.dup.each do |name, association_options|
203
+ next unless object
204
+ association_value = send(name)
458
205
 
459
- method << " h = {}\n"
206
+ serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options)
460
207
 
461
- _attributes.each do |name,key|
462
- method << " h[:\"#{key}\"] = read_attribute_for_serialization(:\"#{name}\") if include?(:\"#{name}\")\n"
208
+ if serializer_class
209
+ serializer = serializer_class.new(
210
+ association_value,
211
+ options.except(:serializer).merge(serializer_from_options(association_options))
212
+ )
213
+ elsif !association_value.nil? && !association_value.instance_of?(Object)
214
+ association_options[:association_options][:virtual_value] = association_value
463
215
  end
464
- method << " h\nend"
465
216
 
466
- self.class.class_eval method
467
- _fast_attributes
217
+ if block_given?
218
+ block.call(name, serializer, association_options[:association_options])
219
+ end
220
+ end
468
221
  end
469
222
 
470
- # Returns options[:scope]
471
- def scope
472
- @options[:scope]
223
+ def serializer_from_options(options)
224
+ opts = {}
225
+ serializer = options.fetch(:association_options, {}).fetch(:serializer, nil)
226
+ opts[:serializer] = serializer if serializer
227
+ opts
473
228
  end
474
229
 
475
- alias :read_attribute_for_serialization :send
476
-
477
- def _serializable_hash
478
- return nil if @object.nil?
479
- @node = attributes
480
- include_associations! if _embed
481
- @node
230
+ def self.serializers_cache
231
+ @serializers_cache ||= ThreadSafe::Cache.new
482
232
  end
483
233
 
484
- def perform_caching?
485
- perform_caching && cache && respond_to?(:cache_key)
486
- end
234
+ private
487
235
 
488
- def expand_cache_key(*args)
489
- ActiveSupport::Cache.expand_cache_key(args)
490
- end
236
+ attr_reader :options
491
237
 
492
- # Use ActiveSupport::Notifications to send events to external systems.
493
- # The event name is: name.class_name.serializer
494
- def instrument(name, payload = {}, &block)
495
- event_name = INSTRUMENT[name]
496
- ActiveSupport::Notifications.instrument(event_name, payload, &block)
497
- end
498
- end
499
-
500
- # DefaultSerializer
501
- #
502
- # Provides a constant interface for all items, particularly
503
- # for ArraySerializer.
504
- class DefaultSerializer
505
- attr_reader :object, :options
238
+ def self.get_serializer_for(klass)
239
+ serializers_cache.fetch_or_store(klass) do
240
+ serializer_class_name = "#{klass.name}Serializer"
241
+ serializer_class = serializer_class_name.safe_constantize
506
242
 
507
- def initialize(object, options={})
508
- @object, @options = object, options
243
+ if serializer_class
244
+ serializer_class
245
+ elsif klass.superclass
246
+ get_serializer_for(klass.superclass)
247
+ end
248
+ end
509
249
  end
510
250
 
511
- def serializable_hash
512
- @object.as_json(@options)
513
- end
514
251
  end
515
252
  end