active_model_serializers 0.8.3 → 0.9.4

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