active_model_serializers 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e52d190b9a08bf39665a61da10e5c1103b553016
4
+ data.tar.gz: 5ca66e11bde1221cee716a40b48b71f3f3600a09
5
+ SHA512:
6
+ metadata.gz: 95648244da95ed7a5d69aa91079a89098864d1fe7d4b7d827caeb3b45e019f4e07ddf563c34ee154eb43e9501ac485ba624e6abef6c131cb8fbf79a9bfefc4b8
7
+ data.tar.gz: 727fb1fa750281605e22c8830212304eb83ac209c2ab2743e7f8695d763eee5635c97d2db6994678b77620385c50899583fd552eb94ffe821c29d1ba10c5d9cb
data/.travis.yml CHANGED
@@ -5,25 +5,25 @@ rvm:
5
5
  - 1.8.7
6
6
  - 1.9.2
7
7
  - 1.9.3
8
+ - ruby-head
8
9
  - ree
9
- - jruby
10
- - rbx
10
+ - jruby-18mode
11
+ - jruby-19mode
12
+ - rbx-18mode
13
+ - rbx-19mode
11
14
  gemfile:
12
15
  - Gemfile
13
- - gemfiles/Gemfile.edge-rails
16
+ - Gemfile.edge
14
17
  matrix:
15
18
  exclude:
16
19
  # Edge Rails is only compatible with 1.9.3
17
- - gemfile: gemfiles/Gemfile.edge-rails
20
+ - gemfile: Gemfile.edge
18
21
  rvm: 1.8.7
19
- - gemfile: gemfiles/Gemfile.edge-rails
22
+ - gemfile: Gemfile.edge
20
23
  rvm: 1.9.2
21
- - gemfile: gemfiles/Gemfile.edge-rails
24
+ - gemfile: Gemfile.edge
22
25
  rvm: ree
23
- - gemfile: gemfiles/Gemfile.edge-rails
24
- rvm: jruby
25
- - gemfile: gemfiles/Gemfile.edge-rails
26
- rvm: rbx
27
- allow_failures:
28
- - gemfile: gemfiles/Gemfile.edge-rails
29
- rvm: 1.9.3
26
+ - gemfile: Gemfile.edge
27
+ rvm: jruby-18mode
28
+ - gemfile: Gemfile.edge
29
+ rvm: rbx-18mode
@@ -1,6 +1,6 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gemspec :path => '..'
3
+ gemspec
4
4
 
5
5
  gem 'rails', github: 'rails/rails'
6
6
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://secure.travis-ci.org/josevalim/active_model_serializers.png)](http://travis-ci.org/josevalim/active_model_serializers)
1
+ [![Build Status](https://secure.travis-ci.org/rails-api/active_model_serializers.png)](http://travis-ci.org/rails-api/active_model_serializers)
2
2
 
3
3
  # Purpose
4
4
 
@@ -15,11 +15,11 @@ development.**
15
15
 
16
16
  # Installing Serializers
17
17
 
18
- For now, the easiest way to install `ActiveModel::Serializers` is to add this
18
+ For now, the easiest way to install `ActiveModel::Serializers` is to add it
19
19
  to your `Gemfile`:
20
20
 
21
21
  ```ruby
22
- gem "active_model_serializers", :git => "git://github.com/josevalim/active_model_serializers.git"
22
+ gem "active_model_serializers", "~> 0.6.0"
23
23
  ```
24
24
 
25
25
  Then, install it on the command line:
@@ -34,7 +34,7 @@ The easiest way to create a new serializer is to generate a new resource, which
34
34
  will generate a serializer at the same time:
35
35
 
36
36
  ```
37
- $ rails g resource post title:string body:string
37
+ $ rails g resource post title:string body:string
38
38
  ```
39
39
 
40
40
  This will generate a serializer in `app/serializers/post_serializer.rb` for
@@ -45,6 +45,14 @@ the serializer generator:
45
45
  $ rails g serializer post
46
46
  ```
47
47
 
48
+ ### Support for PORO's and other ORM's.
49
+
50
+ Currently `ActiveModel::Serializers` adds serialization support to all models
51
+ that descend from `ActiveRecord`. If you are using another ORM or if you are
52
+ using objects that are `ActiveModel` compliant, but do not descend from
53
+ `ActiveRecord`. You must add an include statement for
54
+ `ActiveModel::SerializerSupport`.
55
+
48
56
  # ActiveModel::Serializer
49
57
 
50
58
  All new serializers descend from ActiveModel::Serializer
@@ -64,7 +72,7 @@ end
64
72
  ```
65
73
 
66
74
  In this case, Rails will look for a serializer named `PostSerializer`, and if
67
- it exists, use it to serialize the `Post`.
75
+ it exists, use it to serialize the `Post`.
68
76
 
69
77
  This also works with `respond_with`, which uses `to_json` under the hood. Also
70
78
  note that any options passed to `render :json` will be passed to your
@@ -132,7 +140,9 @@ more concise json. To disable the root element for arrays, you have 3 options:
132
140
  #### 1. Disable root globally for in `ArraySerializer`. In an initializer:
133
141
 
134
142
  ```ruby
135
- ActiveModel::ArraySerializer.root = false
143
+ ActiveSupport.on_load(:active_model_serializers) do
144
+ ActiveModel::ArraySerializer.root = false
145
+ end
136
146
  ```
137
147
 
138
148
  #### 2. Disable root per render call in your controller:
@@ -167,6 +177,19 @@ To specify a custom serializer for the items within an array:
167
177
  ```ruby
168
178
  render :json => @posts, :each_serializer => FancyPostSerializer
169
179
  ```
180
+ #### 4. Define default_serializer_options in your controller
181
+
182
+ If you define `default_serializer_options` method in your controller,
183
+ all serializers in actions of this controller and it's children will use them.
184
+ One of options may be `root: false`
185
+
186
+ ```ruby
187
+ def default_serializer_options
188
+ {
189
+ root: false
190
+ }
191
+ end
192
+ ```
170
193
 
171
194
  ## Getting the old version
172
195
 
@@ -207,8 +230,7 @@ end
207
230
  ```
208
231
 
209
232
  Within a serializer's methods, you can access the object being
210
- serialized as either `object` or the name of the serialized object
211
- (e.g. `admin_comment` for the `AdminCommentSerializer`).
233
+ serialized as `object`.
212
234
 
213
235
  You can also access the `scope` method, which provides an
214
236
  authorization context to your serializer. By default, scope
@@ -243,6 +265,43 @@ class PostSerializer < ActiveModel::Serializer
243
265
  end
244
266
  ```
245
267
 
268
+ If you would like to add meta information to the outputted JSON, use the `:meta`
269
+ option:
270
+
271
+ ```ruby
272
+ render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}
273
+ ```
274
+
275
+ The above usage of `:meta` will produce the following:
276
+
277
+ ```json
278
+ {
279
+ "meta": { "total": 10 },
280
+ "posts": [
281
+ { "title": "Post 1", "body": "Hello!" },
282
+ { "title": "Post 2", "body": "Goodbye!" }
283
+ ]
284
+ }
285
+ ```
286
+
287
+ If you would like to change the meta key name you can use the `:meta_key` option:
288
+
289
+ ```ruby
290
+ render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}, :meta_key => 'meta_object'
291
+ ```
292
+
293
+ The above usage of `:meta_key` will produce the following:
294
+
295
+ ```json
296
+ {
297
+ "meta_object": { "total": 10 },
298
+ "posts": [
299
+ { "title": "Post 1", "body": "Hello!" },
300
+ { "title": "Post 2", "body": "Goodbye!" }
301
+ ]
302
+ }
303
+ ```
304
+
246
305
  If you would like direct, low-level control of attribute serialization, you can
247
306
  completely override the `attributes` method to return the hash you need:
248
307
 
@@ -280,7 +339,7 @@ class PostSerializer < ActiveModel::Serializer
280
339
 
281
340
  # only let the user see comments he created.
282
341
  def comments
283
- post.comments.where(:created_by => scope)
342
+ object.comments.where(:created_by => scope)
284
343
  end
285
344
  end
286
345
  ```
@@ -307,7 +366,7 @@ class PostSerializer < ActiveModel::Serializer
307
366
  has_many :comments
308
367
 
309
368
  def include_comments?
310
- !post.comments_disabled?
369
+ !object.comments_disabled?
311
370
  end
312
371
  end
313
372
  ```
@@ -376,7 +435,7 @@ Now, any associations will be supplied as an Array of IDs:
376
435
  "id": 1,
377
436
  "title": "New post",
378
437
  "body": "A body!",
379
- "comments": [ 1, 2, 3 ]
438
+ "comment_ids": [ 1, 2, 3 ]
380
439
  }
381
440
  }
382
441
  ```
@@ -386,7 +445,7 @@ Alternatively, you can choose to embed only the ids or the associated objects pe
386
445
  ```ruby
387
446
  class PostSerializer < ActiveModel::Serializer
388
447
  attributes :id, :title, :body
389
-
448
+
390
449
  has_many :comments, embed: :objects
391
450
  has_many :tags, embed: :ids
392
451
  end
@@ -403,7 +462,7 @@ The JSON will look like this:
403
462
  "comments": [
404
463
  { "id": 1, "body": "what a dumb post" }
405
464
  ],
406
- "tags": [ 1, 2, 3 ]
465
+ "tag_ids": [ 1, 2, 3 ]
407
466
  }
408
467
  }
409
468
  ```
@@ -434,11 +493,11 @@ this:
434
493
  "id": 1,
435
494
  "title": "New post",
436
495
  "body": "A body!",
437
- "comments": [ 1, 2 ]
496
+ "comment_ids": [ 1, 2 ]
438
497
  },
439
498
  "comments": [
440
- { "id": 1, "body": "what a dumb post", "tags": [ 1, 2 ] },
441
- { "id": 2, "body": "i liked it", "tags": [ 1, 3 ] },
499
+ { "id": 1, "body": "what a dumb post", "tag_ids": [ 1, 2 ] },
500
+ { "id": 2, "body": "i liked it", "tag_ids": [ 1, 3 ] },
442
501
  ],
443
502
  "tags": [
444
503
  { "id": 1, "name": "short" },
@@ -476,6 +535,34 @@ This would generate JSON that would look like this:
476
535
  }
477
536
  ```
478
537
 
538
+ You can also specify a different attribute to use rather than the ID of the
539
+ objects:
540
+
541
+ ```ruby
542
+ class PostSerializer < ActiveModel::Serializer
543
+ embed :ids, :include => true
544
+
545
+ attributes :id, :title, :body
546
+ has_many :comments, :embed_key => :external_id
547
+ end
548
+ ```
549
+
550
+ This would generate JSON that would look like this:
551
+
552
+ ```json
553
+ {
554
+ "post": {
555
+ "id": 1,
556
+ "title": "New post",
557
+ "body": "A body!",
558
+ "comment_ids": [ "COMM001" ]
559
+ },
560
+ "comments": [
561
+ { "id": 1, "external_id": "COMM001", "body": "what a dumb post" }
562
+ ]
563
+ }
564
+ ```
565
+
479
566
  **NOTE**: The `embed :ids` mechanism is primary useful for clients that process
480
567
  data in bulk and load it into a local store. For these clients, the ability to
481
568
  easily see all of the data per type, rather than having to recursively scan the
@@ -496,3 +583,41 @@ class ApplicationController < ActionController::Base
496
583
  serialization_scope :current_admin
497
584
  end
498
585
  ```
586
+
587
+ Please note that, until now, `serialization_scope` doesn't accept a second
588
+ object with options for specifying which actions should or should not take a
589
+ given scope in consideration.
590
+
591
+ To be clear, it's not possible, yet, to do something like this:
592
+
593
+ ```ruby
594
+ class SomeController < ApplicationController
595
+ serialization_scope :current_admin, :except => [:index, :show]
596
+ end
597
+ ```
598
+
599
+ So, in order to have a fine grained control of what each action should take in
600
+ consideration for its scope, you may use something like this:
601
+
602
+ ```ruby
603
+ class CitiesController < ApplicationController
604
+ serialization_scope nil
605
+
606
+ def index
607
+ @cities = City.all
608
+
609
+ render :json => @cities, :each_serializer => CitySerializer
610
+ end
611
+
612
+ def show
613
+ @city = City.find(params[:id])
614
+
615
+ render :json => @city, :scope => current_admin?
616
+ end
617
+ end
618
+ ```
619
+
620
+ Assuming that the `current_admin?` method needs to make a query in the database
621
+ for the current user, the advantage of this approach is that, by setting
622
+ `serialization_scope` to `nil`, the `index` action no longer will need to make
623
+ that query, only the `show` action will.
data/RELEASE_NOTES.md CHANGED
@@ -1,4 +1,4 @@
1
- # VERSION 0.6 (Oct 22, 2012)
1
+ # VERSION 0.6 (to be released)
2
2
 
3
3
  * Serialize sets properly
4
4
  * Add root option to ArraySerializer
@@ -40,10 +40,17 @@ module ActionController
40
40
  end
41
41
 
42
42
  def _render_option_json(json, options)
43
+ options = default_serializer_options.merge(options) if default_serializer_options
44
+
43
45
  serializer = options.delete(:serializer) ||
44
46
  (json.respond_to?(:active_model_serializer) && json.active_model_serializer)
45
47
 
46
48
  if json.respond_to?(:to_ary)
49
+ unless serializer <= ActiveModel::ArraySerializer
50
+ raise ArgumentError.new("#{serializer.name} is not an ArraySerializer. " +
51
+ "You may want to use the :each_serializer option instead.")
52
+ end
53
+
47
54
  if options[:root] != false && serializer.root != false
48
55
  # default root element for arrays is serializer's root or the controller name
49
56
  # the serializer for an Array is ActiveModel::ArraySerializer
@@ -54,7 +61,7 @@ module ActionController
54
61
  if serializer
55
62
  options[:scope] = serialization_scope unless options.has_key?(:scope)
56
63
  options[:url_options] = url_options
57
- json = serializer.new(json, options.merge(default_serializer_options || {}))
64
+ json = serializer.new(json, options)
58
65
  end
59
66
  super
60
67
  end
@@ -1,4 +1,6 @@
1
1
  require "active_support/core_ext/class/attribute"
2
+ require 'active_support/dependencies'
3
+ require 'active_support/descendants_tracker'
2
4
 
3
5
  module ActiveModel
4
6
  # Active Model Array Serializer
@@ -11,6 +13,8 @@ module ActiveModel
11
13
  # ActiveModel::ArraySerializer.root = false
12
14
  #
13
15
  class ArraySerializer
16
+ extend ActiveSupport::DescendantsTracker
17
+
14
18
  attr_reader :object, :options
15
19
 
16
20
  class_attribute :root
@@ -27,30 +31,34 @@ module ActiveModel
27
31
  serializer = item.active_model_serializer
28
32
  end
29
33
 
30
- if serializer
31
- serializer.new(item, @options)
34
+ serializable = serializer ? serializer.new(item, @options) : item
35
+
36
+ if serializable.respond_to?(:serializable_hash)
37
+ serializable.serializable_hash
32
38
  else
33
- item
39
+ serializable.as_json
34
40
  end
35
41
  end
36
42
  end
37
43
 
44
+ def meta_key
45
+ @options[:meta_key].try(:to_sym) || :meta
46
+ end
47
+
48
+ def include_meta(hash)
49
+ hash[meta_key] = @options[:meta] if @options.has_key?(:meta)
50
+ end
51
+
38
52
  def as_json(*args)
39
53
  @options[:hash] = hash = {}
40
54
  @options[:unique_values] = {}
41
55
 
42
- array = serializable_array.map do |item|
43
- if item.respond_to?(:serializable_hash)
44
- item.serializable_hash
45
- else
46
- item.as_json
47
- end
48
- end
49
-
50
56
  if root = @options[:root]
51
- hash.merge!(root => array)
57
+ hash.merge!(root => serializable_array)
58
+ include_meta hash
59
+ hash
52
60
  else
53
- array
61
+ serializable_array
54
62
  end
55
63
  end
56
64
  end
@@ -1,6 +1,7 @@
1
1
  require "active_support/core_ext/class/attribute"
2
2
  require "active_support/core_ext/module/anonymous"
3
- require "set"
3
+ require 'active_support/dependencies'
4
+ require 'active_support/descendants_tracker'
4
5
 
5
6
  module ActiveModel
6
7
  # Active Model Serializer
@@ -37,6 +38,8 @@ module ActiveModel
37
38
  # end
38
39
  #
39
40
  class Serializer
41
+ extend ActiveSupport::DescendantsTracker
42
+
40
43
  INCLUDE_METHODS = {}
41
44
  INSTRUMENT = { :serialize => :"serialize.serializer", :associations => :"associations.serializer" }
42
45
 
@@ -52,178 +55,6 @@ module ActiveModel
52
55
  end
53
56
  end
54
57
 
55
- module Associations #:nodoc:
56
- class Config #:nodoc:
57
- class_attribute :options
58
-
59
- def self.refine(name, class_options)
60
- current_class = self
61
-
62
- Class.new(self) do
63
- singleton_class.class_eval do
64
- define_method(:to_s) do
65
- "(subclass of #{current_class.name})"
66
- end
67
-
68
- alias inspect to_s
69
- end
70
-
71
- self.options = class_options
72
- end
73
- end
74
-
75
- self.options = {}
76
-
77
- def initialize(name, source, options={})
78
- @name = name
79
- @source = source
80
- @options = options
81
- end
82
-
83
- def option(key, default=nil)
84
- if @options.key?(key)
85
- @options[key]
86
- elsif self.class.options.key?(key)
87
- self.class.options[key]
88
- else
89
- default
90
- end
91
- end
92
-
93
- def target_serializer
94
- option(:serializer)
95
- end
96
-
97
- def source_serializer
98
- @source
99
- end
100
-
101
- def key
102
- option(:key) || @name
103
- end
104
-
105
- def root
106
- option(:root) || plural_key
107
- end
108
-
109
- def name
110
- option(:name) || @name
111
- end
112
-
113
- def associated_object
114
- option(:value) || source_serializer.send(name)
115
- end
116
-
117
- def embed_ids?
118
- option(:embed, source_serializer._embed) == :ids
119
- end
120
-
121
- def embed_objects?
122
- option(:embed, source_serializer._embed) == :objects
123
- end
124
-
125
- def embed_in_root?
126
- option(:include, source_serializer._root_embed)
127
- end
128
-
129
- def embeddable?
130
- !associated_object.nil?
131
- end
132
-
133
- protected
134
-
135
- def find_serializable(object)
136
- if target_serializer
137
- target_serializer.new(object, source_serializer.options)
138
- elsif object.respond_to?(:active_model_serializer) && (ams = object.active_model_serializer)
139
- ams.new(object, source_serializer.options)
140
- else
141
- object
142
- end
143
- end
144
- end
145
-
146
- class HasMany < Config #:nodoc:
147
- alias plural_key key
148
-
149
- def serialize
150
- associated_object.map do |item|
151
- find_serializable(item).serializable_hash
152
- end
153
- end
154
- alias serialize_many serialize
155
-
156
- def serialize_ids
157
- # Use pluck or select_columns if available
158
- # return collection.ids if collection.respond_to?(:ids)
159
-
160
- associated_object.map do |item|
161
- item.read_attribute_for_serialization(:id)
162
- end
163
- end
164
- end
165
-
166
- class HasOne < Config #:nodoc:
167
- def embeddable?
168
- if polymorphic? && associated_object.nil?
169
- false
170
- else
171
- true
172
- end
173
- end
174
-
175
- def polymorphic?
176
- option :polymorphic
177
- end
178
-
179
- def polymorphic_key
180
- associated_object.class.to_s.demodulize.underscore.to_sym
181
- end
182
-
183
- def plural_key
184
- if polymorphic?
185
- associated_object.class.to_s.pluralize.demodulize.underscore.to_sym
186
- else
187
- key.to_s.pluralize.to_sym
188
- end
189
- end
190
-
191
- def serialize
192
- object = associated_object
193
-
194
- if object && polymorphic?
195
- {
196
- :type => polymorphic_key,
197
- polymorphic_key => find_serializable(object).serializable_hash
198
- }
199
- elsif object
200
- find_serializable(object).serializable_hash
201
- end
202
- end
203
-
204
- def serialize_many
205
- object = associated_object
206
- value = object && find_serializable(object).serializable_hash
207
- value ? [value] : []
208
- end
209
-
210
- def serialize_ids
211
- object = associated_object
212
-
213
- if object && polymorphic?
214
- {
215
- :type => polymorphic_key,
216
- :id => object.read_attribute_for_serialization(:id)
217
- }
218
- elsif object
219
- object.read_attribute_for_serialization(:id)
220
- else
221
- nil
222
- end
223
- end
224
- end
225
- end
226
-
227
58
  class_attribute :_attributes
228
59
  self._attributes = {}
229
60
 
@@ -238,10 +69,15 @@ module ActiveModel
238
69
  class << self
239
70
  # Define attributes to be used in the serialization.
240
71
  def attributes(*attrs)
72
+
241
73
  self._attributes = _attributes.dup
242
74
 
243
75
  attrs.each do |attr|
244
- attribute attr
76
+ if Hash === attr
77
+ attr.each {|attr_real, key| attribute attr_real, :key => key }
78
+ else
79
+ attribute attr
80
+ end
245
81
  end
246
82
  end
247
83
 
@@ -342,16 +178,31 @@ module ActiveModel
342
178
  klass = model_class
343
179
  columns = klass.columns_hash
344
180
 
345
- attrs = _attributes.inject({}) do |hash, (name,key)|
346
- column = columns[name.to_s]
347
- hash.merge key => column.type
181
+ attrs = {}
182
+ _attributes.each do |name, key|
183
+ if column = columns[name.to_s]
184
+ attrs[key] = column.type
185
+ else
186
+ # Computed attribute (method on serializer or model). We cannot
187
+ # infer the type, so we put nil.
188
+ attrs[key] = nil
189
+ end
348
190
  end
349
191
 
350
- associations = _associations.inject({}) do |hash, (attr,association_class)|
192
+ associations = {}
193
+ _associations.each do |attr, association_class|
351
194
  association = association_class.new(attr, self)
352
195
 
353
- model_association = klass.reflect_on_association(association.name)
354
- hash.merge association.key => { model_association.macro => model_association.name }
196
+ if model_association = klass.reflect_on_association(association.name)
197
+ # Real association.
198
+ associations[association.key] = { model_association.macro => model_association.name }
199
+ else
200
+ # Computed association. We could infer has_many vs. has_one from
201
+ # the association class, but that would make it different from
202
+ # real associations, which read has_one vs. belongs_to from the
203
+ # model.
204
+ associations[association.key] = nil
205
+ end
355
206
  end
356
207
 
357
208
  { :attributes => attrs, :associations => associations }
@@ -377,16 +228,7 @@ module ActiveModel
377
228
  def root(name)
378
229
  self._root = name
379
230
  end
380
-
381
- def inherited(klass) #:nodoc:
382
- return if klass.anonymous?
383
- name = klass.name.demodulize.underscore.sub(/_serializer$/, '')
384
-
385
- klass.class_eval do
386
- alias_method name.to_sym, :object
387
- root name.to_sym unless self._root == false
388
- end
389
- end
231
+ alias_method :root=, :root
390
232
  end
391
233
 
392
234
  attr_reader :object, :options
@@ -395,18 +237,34 @@ module ActiveModel
395
237
  @object, @options = object, options
396
238
  end
397
239
 
240
+ def root_name
241
+ return false if self._root == false
242
+
243
+ class_name = self.class.name.demodulize.underscore.sub(/_serializer$/, '').to_sym unless self.class.name.blank?
244
+ self._root || class_name
245
+ end
246
+
398
247
  def url_options
399
248
  @options[:url_options] || {}
400
249
  end
401
250
 
251
+ def meta_key
252
+ @options[:meta_key].try(:to_sym) || :meta
253
+ end
254
+
255
+ def include_meta(hash)
256
+ hash[meta_key] = @options[:meta] if @options.has_key?(:meta)
257
+ end
258
+
402
259
  # Returns a json representation of the serializable
403
260
  # object including the root.
404
261
  def as_json(options={})
405
- if root = options.fetch(:root, @options.fetch(:root, _root))
262
+ if root = options.fetch(:root, @options.fetch(:root, root_name))
406
263
  @options[:hash] = hash = {}
407
264
  @options[:unique_values] = {}
408
265
 
409
266
  hash.merge!(root => serializable_hash)
267
+ include_meta hash
410
268
  hash
411
269
  else
412
270
  serializable_hash
@@ -416,6 +274,7 @@ module ActiveModel
416
274
  # Returns a hash representation of the serializable
417
275
  # object without the root.
418
276
  def serializable_hash
277
+ return nil if @object.nil?
419
278
  instrument(:serialize, :serializer => self.class.name) do
420
279
  @node = attributes
421
280
  instrument :associations do
@@ -482,7 +341,7 @@ module ActiveModel
482
341
  if association.embed_in_root? && hash.nil?
483
342
  raise IncludeError.new(self.class, association.name)
484
343
  elsif association.embed_in_root? && association.embeddable?
485
- merge_association hash, association.root, association.serialize_many, unique_values
344
+ merge_association hash, association.root, association.serializables, unique_values
486
345
  end
487
346
  elsif association.embed_objects?
488
347
  node[association.key] = association.serialize
@@ -498,13 +357,15 @@ module ActiveModel
498
357
  # a unique list of all of the objects that are already in the Array. This
499
358
  # avoids the need to scan through the Array looking for entries every time
500
359
  # we want to merge a new list of values.
501
- def merge_association(hash, key, value, unique_values)
502
- if current_value = unique_values[key]
503
- current_value.merge! value
504
- hash[key] = current_value.to_a
505
- elsif value
506
- hash[key] = value
507
- unique_values[key] = OrderedSet.new(value)
360
+ def merge_association(hash, key, serializables, unique_values)
361
+ already_serialized = (unique_values[key] ||= {})
362
+ serializable_hashes = (hash[key] ||= [])
363
+
364
+ serializables.each do |serializable|
365
+ unless already_serialized.include? serializable.object
366
+ already_serialized[serializable.object] = true
367
+ serializable_hashes << serializable.serializable_hash
368
+ end
508
369
  end
509
370
  end
510
371