active_model_serializers 0.8.3 → 0.9.0

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -5
  3. data/CONTRIBUTING.md +20 -0
  4. data/DESIGN.textile +4 -4
  5. data/{MIT-LICENSE.txt → MIT-LICENSE} +0 -0
  6. data/README.md +307 -99
  7. data/lib/action_controller/serialization.rb +35 -16
  8. data/lib/action_controller/serialization_test_case.rb +79 -0
  9. data/lib/active_model/array_serializer.rb +40 -79
  10. data/lib/active_model/default_serializer.rb +32 -0
  11. data/lib/active_model/serializable.rb +40 -0
  12. data/lib/active_model/serializer/associations.rb +71 -202
  13. data/lib/active_model/serializer/config.rb +31 -0
  14. data/lib/active_model/serializer/generators/resource_override.rb +13 -0
  15. data/lib/{generators → active_model/serializer/generators}/serializer/USAGE +0 -0
  16. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +14 -0
  17. data/lib/active_model/serializer/generators/serializer/serializer_generator.rb +37 -0
  18. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +93 -0
  19. data/lib/active_model/serializer/generators/serializer/templates/serializer.rb +8 -0
  20. data/lib/active_model/serializer/railtie.rb +10 -0
  21. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  22. data/lib/active_model/serializer.rb +186 -433
  23. data/lib/active_model/serializer_support.rb +5 -0
  24. data/lib/active_model_serializers.rb +13 -88
  25. data/test/fixtures/active_record.rb +92 -0
  26. data/test/fixtures/poro.rb +75 -0
  27. data/test/integration/action_controller/serialization_test.rb +287 -0
  28. data/test/integration/action_controller/serialization_test_case_test.rb +61 -0
  29. data/test/integration/active_record/active_record_test.rb +77 -0
  30. data/test/integration/generators/resource_generator_test.rb +26 -0
  31. data/test/integration/generators/scaffold_controller_generator_test.rb +64 -0
  32. data/test/integration/generators/serializer_generator_test.rb +41 -0
  33. data/test/test_app.rb +11 -0
  34. data/test/test_helper.rb +10 -18
  35. data/test/unit/active_model/array_serializer/except_test.rb +18 -0
  36. data/test/unit/active_model/array_serializer/key_format_test.rb +18 -0
  37. data/test/unit/active_model/array_serializer/meta_test.rb +53 -0
  38. data/test/unit/active_model/array_serializer/only_test.rb +18 -0
  39. data/test/unit/active_model/array_serializer/root_test.rb +102 -0
  40. data/test/unit/active_model/array_serializer/scope_test.rb +24 -0
  41. data/test/unit/active_model/array_serializer/serialization_test.rb +199 -0
  42. data/test/unit/active_model/default_serializer_test.rb +13 -0
  43. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +21 -0
  44. data/test/unit/active_model/serializer/associations_test.rb +19 -0
  45. data/test/unit/active_model/serializer/attributes_test.rb +41 -0
  46. data/test/unit/active_model/serializer/config_test.rb +88 -0
  47. data/test/unit/active_model/serializer/filter_test.rb +69 -0
  48. data/test/unit/active_model/serializer/has_many_test.rb +230 -0
  49. data/test/unit/active_model/serializer/has_one_test.rb +207 -0
  50. data/test/unit/active_model/serializer/key_format_test.rb +25 -0
  51. data/test/unit/active_model/serializer/meta_test.rb +39 -0
  52. data/test/unit/active_model/serializer/options_test.rb +15 -0
  53. data/test/unit/active_model/serializer/root_test.rb +117 -0
  54. data/test/unit/active_model/serializer/scope_test.rb +49 -0
  55. metadata +86 -62
  56. data/.gitignore +0 -18
  57. data/.travis.yml +0 -28
  58. data/Gemfile +0 -4
  59. data/Gemfile.edge +0 -9
  60. data/Rakefile +0 -18
  61. data/active_model_serializers.gemspec +0 -24
  62. data/bench/perf.rb +0 -43
  63. data/cruft.md +0 -19
  64. data/lib/active_record/serializer_override.rb +0 -16
  65. data/lib/generators/resource_override.rb +0 -13
  66. data/lib/generators/serializer/serializer_generator.rb +0 -42
  67. data/lib/generators/serializer/templates/serializer.rb +0 -19
  68. data/test/array_serializer_test.rb +0 -75
  69. data/test/association_test.rb +0 -592
  70. data/test/caching_test.rb +0 -96
  71. data/test/generators_test.rb +0 -85
  72. data/test/no_serialization_scope_test.rb +0 -34
  73. data/test/serialization_scope_name_test.rb +0 -67
  74. data/test/serialization_test.rb +0 -392
  75. data/test/serializer_support_test.rb +0 -51
  76. data/test/serializer_test.rb +0 -1465
  77. data/test/test_fakes.rb +0 -217
@@ -1,515 +1,268 @@
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 'active_model/array_serializer'
2
+ require 'active_model/serializable'
3
+ require 'active_model/serializer/associations'
4
+ require 'active_model/serializer/config'
5
+
6
+ require 'thread'
5
7
 
6
8
  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
9
  class Serializer
41
- extend ActiveSupport::DescendantsTracker
42
-
43
- INCLUDE_METHODS = {}
44
- INSTRUMENT = { :serialize => :"serialize.serializer", :associations => :"associations.serializer" }
45
-
46
- class IncludeError < StandardError
47
- attr_reader :source, :association
48
-
49
- def initialize(source, association)
50
- @source, @association = source, association
51
- end
52
-
53
- def to_s
54
- "Cannot serialize #{association} when #{source} does not have a root!"
55
- end
56
- end
10
+ include Serializable
57
11
 
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
12
+ @mutex = Mutex.new
71
13
 
72
14
  class << self
73
- # set perform caching like root
74
- def cached(value = true)
75
- self.perform_caching = value
76
- end
77
-
78
- # Define attributes to be used in the serialization.
79
- def attributes(*attrs)
80
-
81
- self._attributes = _attributes.dup
82
-
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
15
+ def inherited(base)
16
+ base._root = _root
17
+ base._attributes = (_attributes || []).dup
18
+ base._associations = (_associations || {}).dup
90
19
  end
91
20
 
92
- def attribute(attr, options={})
93
- self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym})
94
-
95
- attr = attr.keys[0] if attr.is_a? Hash
96
-
97
- unless method_defined?(attr)
98
- 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
21
+ def setup
22
+ @mutex.synchronize do
23
+ yield CONFIG
111
24
  end
112
-
113
25
  end
114
26
 
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
27
+ EMBED_IN_ROOT_OPTIONS = [
28
+ :include,
29
+ :embed_in_root,
30
+ :embed_in_root_key,
31
+ :embed_namespace
32
+ ].freeze
127
33
 
128
- self._associations[attr] = klass.refine(attr, options)
34
+ def embed(type, options={})
35
+ CONFIG.embed = type
36
+ if EMBED_IN_ROOT_OPTIONS.any? { |opt| options[opt].present? }
37
+ CONFIG.embed_in_root = true
129
38
  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
140
- end
39
+ if options[:embed_in_root_key].present?
40
+ CONFIG.embed_in_root_key = options[:embed_in_root_key]
141
41
  end
42
+ ActiveSupport::Deprecation.warn <<-WARN
43
+ ** Notice: embed is deprecated. **
44
+ The use of .embed method on a Serializer will be soon removed, as this should have a global scope and not a class scope.
45
+ Please use the global .setup method instead:
46
+ ActiveModel::Serializer.setup do |config|
47
+ config.embed = :#{type}
48
+ config.embed_in_root = #{CONFIG.embed_in_root || false}
49
+ end
50
+ WARN
142
51
  end
143
52
 
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
-
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)
53
+ def format_keys(format)
54
+ @key_format = format
162
55
  end
56
+ attr_reader :key_format
163
57
 
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
58
+ if RUBY_VERSION >= '2.0'
59
+ def serializer_for(resource)
60
+ if resource.respond_to?(:to_ary)
61
+ if Object.constants.include?(:ArraySerializer)
62
+ ::ArraySerializer
209
63
  else
210
- attrs[key] = nil
64
+ ArraySerializer
65
+ end
66
+ else
67
+ begin
68
+ Object.const_get "#{resource.class.name}Serializer"
69
+ rescue NameError
70
+ nil
211
71
  end
212
72
  end
213
73
  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 }
74
+ else
75
+ def serializer_for(resource)
76
+ if resource.respond_to?(:to_ary)
77
+ if Object.constants.include?(:ArraySerializer)
78
+ ::ArraySerializer
79
+ else
80
+ ArraySerializer
81
+ end
222
82
  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
83
+ "#{resource.class.name}Serializer".safe_constantize
228
84
  end
229
85
  end
230
-
231
- { :attributes => attrs, :associations => associations }
232
86
  end
233
87
 
234
- # The model class associated with this serializer.
235
- def model_class
236
- name.sub(/Serializer$/, '').constantize
237
- end
88
+ attr_accessor :_root, :_attributes, :_associations
89
+ alias root _root=
90
+ alias root= _root=
238
91
 
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]
92
+ def root_name
93
+ name.demodulize.underscore.sub(/_serializer$/, '') if name
248
94
  end
249
95
 
250
- # Defines the root used on serialization. If false, disables the root.
251
- def root(name)
252
- self._root = name
253
- 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
96
+ def attributes(*attrs)
97
+ @_attributes.concat attrs
275
98
 
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
99
+ attrs.each do |attr|
100
+ define_method attr do
101
+ object.read_attribute_for_serialization attr
102
+ end unless method_defined?(attr)
280
103
  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
285
-
286
- serializer.new(resource, options)
287
104
  end
288
- end
289
-
290
- attr_reader :object, :options
291
-
292
- def initialize(object, options={})
293
- @object, @options = object, options
294
105
 
295
- scope_name = @options[:scope_name]
296
- if scope_name && !respond_to?(scope_name)
297
- self.class.class_eval do
298
- define_method scope_name, lambda { scope }
299
- end
106
+ def has_one(*attrs)
107
+ associate(Association::HasOne, *attrs)
300
108
  end
301
- end
302
-
303
- def root_name
304
- return false if self._root == false
305
109
 
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
110
+ def has_many(*attrs)
111
+ associate(Association::HasMany, *attrs)
312
112
  end
313
- end
314
113
 
315
- def url_options
316
- @options[:url_options] || {}
317
- end
114
+ private
318
115
 
319
- def meta_key
320
- @options[:meta_key].try(:to_sym) || :meta
321
- end
116
+ def associate(klass, *attrs)
117
+ options = attrs.extract_options!
322
118
 
323
- def include_meta(hash)
324
- hash[meta_key] = @options[:meta] if @options.has_key?(:meta)
325
- end
119
+ attrs.each do |attr|
120
+ define_method attr do
121
+ object.send attr
122
+ end unless method_defined?(attr)
326
123
 
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
124
+ @_associations[attr] = klass.new(attr, options)
331
125
  end
332
- else
333
- super
334
126
  end
335
127
  end
336
128
 
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
129
+ def initialize(object, options={})
130
+ @object = object
131
+ @scope = options[:scope]
132
+ @root = options.fetch(:root, self.class._root)
133
+ @meta_key = options[:meta_key] || :meta
134
+ @meta = options[@meta_key]
135
+ @wrap_in_array = options[:_wrap_in_array]
136
+ @only = options[:only] ? Array(options[:only]) : nil
137
+ @except = options[:except] ? Array(options[:except]) : nil
138
+ @key_format = options[:key_format]
139
+ @context = options[:context]
351
140
  end
141
+ attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context
352
142
 
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
143
+ def json_key
144
+ key = if root == true || root.nil?
145
+ self.class.root_name
360
146
  else
361
- _serializable_hash
147
+ root
362
148
  end
363
- end
364
149
 
365
- def include_associations!
366
- _associations.each_key do |name|
367
- include!(name) if include?(name)
368
- end
150
+ key_format == :lower_camel && key.present? ? key.camelize(:lower) : key
369
151
  end
370
152
 
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]
153
+ def attributes
154
+ filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
155
+ hash[name] = send(name)
156
+ end
375
157
  end
376
158
 
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
- {}
159
+ def associations
160
+ associations = self.class._associations
161
+ included_associations = filter(associations.keys)
162
+ associations.each_with_object({}) do |(name, association), hash|
163
+ if included_associations.include? name
164
+ if association.embed_ids?
165
+ ids = serialize_ids association
166
+ if association.embed_namespace?
167
+ hash = hash[association.embed_namespace] ||= {}
168
+ hash[association.key] = ids
169
+ else
170
+ hash[association.key] = ids
171
+ end
172
+ elsif association.embed_objects?
173
+ if association.embed_namespace?
174
+ hash = hash[association.embed_namespace] ||= {}
175
+ end
176
+ hash[association.embedded_key] = serialize association
390
177
  end
391
- else
392
- hash = @options[:hash]
393
- @options[:unique_values] ||= {}
394
178
  end
395
-
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
406
-
407
- association_class =
408
- if klass = _associations[name]
409
- klass
410
- elsif value.respond_to?(:to_ary)
411
- Associations::HasMany
412
- else
413
- Associations::HasOne
414
- 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
179
  end
429
180
  end
430
181
 
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
182
+ def filter(keys)
183
+ if @only
184
+ keys & @only
185
+ elsif @except
186
+ keys - @except
187
+ else
188
+ keys
449
189
  end
450
190
  end
451
191
 
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"
458
-
459
- method << " h = {}\n"
192
+ def embedded_in_root_associations
193
+ associations = self.class._associations
194
+ included_associations = filter(associations.keys)
195
+ associations.each_with_object({}) do |(name, association), hash|
196
+ if included_associations.include? name
197
+ if association.embed_in_root?
198
+ if association.embed_in_root_key?
199
+ hash = hash[association.embed_in_root_key] ||= {}
200
+ end
201
+ association_serializer = build_serializer(association)
202
+ hash.merge!(association_serializer.embedded_in_root_associations) {|key, oldval, newval| [newval, oldval].flatten }
460
203
 
461
- _attributes.each do |name,key|
462
- method << " h[:\"#{key}\"] = read_attribute_for_serialization(:\"#{name}\") if include?(:\"#{name}\")\n"
204
+ serialized_data = association_serializer.serializable_object
205
+ key = association.root_key
206
+ if hash.has_key?(key)
207
+ hash[key].concat(serialized_data).uniq!
208
+ else
209
+ hash[key] = serialized_data
210
+ end
211
+ end
463
212
  end
464
- method << " h\nend"
465
-
466
- self.class.class_eval method
467
- _fast_attributes
213
+ end
468
214
  end
469
215
 
470
- # Returns options[:scope]
471
- def scope
472
- @options[:scope]
216
+ def build_serializer(association)
217
+ object = send(association.name)
218
+ association.build_serializer(object, scope: scope)
473
219
  end
474
220
 
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
221
+ def serialize(association)
222
+ build_serializer(association).serializable_object
482
223
  end
483
224
 
484
- def perform_caching?
485
- perform_caching && cache && respond_to?(:cache_key)
225
+ def serialize_ids(association)
226
+ associated_data = send(association.name)
227
+ if associated_data.respond_to?(:to_ary)
228
+ associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) }
229
+ else
230
+ associated_data.read_attribute_for_serialization(association.embed_key) if associated_data
231
+ end
486
232
  end
487
233
 
488
- def expand_cache_key(*args)
489
- ActiveSupport::Cache.expand_cache_key(args)
234
+ def key_format
235
+ @key_format || self.class.key_format || CONFIG.key_format
490
236
  end
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)
238
+ def format_key(key)
239
+ if key_format == :lower_camel
240
+ key.to_s.camelize(:lower)
241
+ else
242
+ key
243
+ end
497
244
  end
498
- end
499
245
 
500
- # DefaultSerializer
501
- #
502
- # Provides a constant interface for all items, particularly
503
- # for ArraySerializer.
504
- class DefaultSerializer
505
- attr_reader :object, :options
246
+ def convert_keys(hash)
247
+ Hash[hash.map do |k,v|
248
+ key = if k.is_a?(Symbol)
249
+ format_key(k).to_sym
250
+ else
251
+ format_key(k)
252
+ end
506
253
 
507
- def initialize(object, options={})
508
- @object, @options = object, options
254
+ [key ,v]
255
+ end]
509
256
  end
510
257
 
511
- def serializable_hash
512
- @object.as_json(@options)
258
+ def serializable_object(options={})
259
+ return @wrap_in_array ? [] : nil if @object.nil?
260
+ hash = attributes
261
+ hash.merge! associations
262
+ hash = convert_keys(hash) if key_format.present?
263
+ @wrap_in_array ? [hash] : hash
513
264
  end
265
+ alias_method :serializable_hash, :serializable_object
514
266
  end
267
+
515
268
  end