active_model_serializers 0.8.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
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