active_model_serializers 0.6.0 → 0.7.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.
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