active_model_serializers 0.1.0 → 0.5.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.
- data/{README.textile → DESIGN.textile} +31 -3
- data/Gemfile +3 -0
- data/MIT-LICENSE.txt +21 -0
- data/README.markdown +270 -0
- data/RELEASE_NOTES.md +4 -0
- data/active_model_serializers.gemspec +5 -1
- data/lib/action_controller/serialization.rb +14 -3
- data/lib/active_model/serializer.rb +231 -78
- data/lib/active_model/serializers/version.rb +5 -0
- data/lib/active_model_serializers.rb +11 -0
- data/lib/generators/resource_override.rb +13 -0
- data/lib/generators/serializer/serializer_generator.rb +1 -1
- data/test/association_test.rb +392 -0
- data/test/generators_test.rb +3 -3
- data/test/serialization_test.rb +50 -4
- data/test/serializer_test.rb +123 -93
- data/test/test_helper.rb +10 -0
- metadata +25 -8
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
<strong>This was the original design document for serializers.</strong> It is useful mostly for historical purposes as the public API has changed.
|
3
2
|
|
4
3
|
h2. Rails Serializers
|
5
4
|
|
@@ -456,7 +455,36 @@ The +association_ids+ helper will use the overridden version of the association,
|
|
456
455
|
this case, +association_ids+ will only include the ids of the comments provided by the
|
457
456
|
+comments+ method.
|
458
457
|
|
459
|
-
|
458
|
+
|
459
|
+
h3. Special Association Serializers
|
460
|
+
|
461
|
+
So far, associations defined in serializers use either the +as_json+ method on the model
|
462
|
+
or the defined serializer for the association type. Sometimes, you may want to serialize
|
463
|
+
associated models differently when they are requested as part of another resource than
|
464
|
+
when they are requested on their own.
|
465
|
+
|
466
|
+
For instance, we might want to provide the full comment when it is requested directly,
|
467
|
+
but only its title when requested as part of the post. To achieve this, you can define
|
468
|
+
a serializer for associated objects nested inside the main serializer.
|
469
|
+
|
470
|
+
<pre lang="ruby">
|
471
|
+
class PostSerializer < ActiveModel::Serializer
|
472
|
+
class CommentSerializer < ActiveModel::Serializer
|
473
|
+
attributes :id, :title
|
474
|
+
end
|
475
|
+
|
476
|
+
# same as before
|
477
|
+
# ...
|
478
|
+
end
|
479
|
+
</pre>
|
480
|
+
|
481
|
+
In other words, if a +PostSerializer+ is trying to serialize comments, it will first
|
482
|
+
look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+
|
483
|
+
and finally +comment.as_json+.
|
484
|
+
|
485
|
+
h3. Overriding the Defaults
|
486
|
+
|
487
|
+
h4. Authorization Scope
|
460
488
|
|
461
489
|
By default, the authorization scope for serializers is +:current_user+. This means
|
462
490
|
that when you call +render json: @post+, the controller will automatically call
|
data/Gemfile
CHANGED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2011-2012 José Valim & Yehuda Katz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.markdown
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
[](http://travis-ci.org/josevalim/active_model_serializers)
|
2
|
+
|
3
|
+
# Purpose
|
4
|
+
|
5
|
+
The purpose of `ActiveModel::Serializers` is to provide an object to
|
6
|
+
encapsulate serialization of `ActiveModel` objects, including `ActiveRecord`
|
7
|
+
objects.
|
8
|
+
|
9
|
+
Serializers know about both a model and the `current_user`, so you can
|
10
|
+
customize serialization based upon whether a user is authorized to see the
|
11
|
+
content.
|
12
|
+
|
13
|
+
In short, **serializers replaces hash-driven development with object-oriented
|
14
|
+
development.**
|
15
|
+
|
16
|
+
# Installing Serializers
|
17
|
+
|
18
|
+
For now, the easiest way to install `ActiveModel::Serializers` is to add this
|
19
|
+
to your `Gemfile`:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem "active_model_serializers", :git => "git://github.com/josevalim/active_model_serializers.git"
|
23
|
+
```
|
24
|
+
|
25
|
+
Then, install it on the command line:
|
26
|
+
|
27
|
+
```
|
28
|
+
$ bundle install
|
29
|
+
```
|
30
|
+
|
31
|
+
# Creating a Serializer
|
32
|
+
|
33
|
+
The easiest way to create a new serializer is to generate a new resource, which
|
34
|
+
will generate a serializer at the same time:
|
35
|
+
|
36
|
+
```
|
37
|
+
$ rails g resource post title:string body:string
|
38
|
+
```
|
39
|
+
|
40
|
+
This will generate a serializer in `app/serializers/post_serializer.rb` for
|
41
|
+
your new model. You can also generate a serializer for an existing model with
|
42
|
+
the `serializer generator`:
|
43
|
+
|
44
|
+
```
|
45
|
+
$ rails g serializer post
|
46
|
+
```
|
47
|
+
|
48
|
+
# ActiveModel::Serializer
|
49
|
+
|
50
|
+
All new serializers descend from ActiveModel::Serializer
|
51
|
+
|
52
|
+
# render :json
|
53
|
+
|
54
|
+
In your controllers, when you use `render :json`, Rails will now first search
|
55
|
+
for a serializer for the object and use it if available.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class PostsController < ApplicationController
|
59
|
+
def show
|
60
|
+
@post = Post.find(params[:id])
|
61
|
+
render :json => @post
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
In this case, Rails will look for a serializer named `PostSerializer`, and if
|
67
|
+
it exists, use it to serialize the `Post`.
|
68
|
+
|
69
|
+
This also works with `render_with`, which uses `to_json` under the hood. Also
|
70
|
+
note that any options passed to `render :json` will be passed to your
|
71
|
+
serializer and available as `@options` inside.
|
72
|
+
|
73
|
+
## Getting the old version
|
74
|
+
|
75
|
+
If you find that your project is already relying on the old rails to_json
|
76
|
+
change `render :json` to `render :json => @your_object.to_json`.
|
77
|
+
|
78
|
+
# Attributes and Associations
|
79
|
+
|
80
|
+
Once you have a serializer, you can specify which attributes and associations
|
81
|
+
you would like to include in the serialized form.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class PostSerializer < ActiveModel::Serializer
|
85
|
+
attributes :id, :title, :body
|
86
|
+
has_many :comments
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
## Attributes
|
91
|
+
|
92
|
+
For specified attributes, the serializer will look up the attribute on the
|
93
|
+
object you passed to `render :json`. It uses
|
94
|
+
`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a
|
95
|
+
regular attribute lookup.
|
96
|
+
|
97
|
+
If you would like the key in the outputted JSON to be different from its name
|
98
|
+
in ActiveRecord, you can use the `:key` option to customize it:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
class PostSerializer < ActiveModel::Serializer
|
102
|
+
attributes :id, :body
|
103
|
+
|
104
|
+
# look up :subject on the model, but use +title+ in the JSON
|
105
|
+
attribute :subject, :key => :title
|
106
|
+
has_many :comments
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
## Associations
|
111
|
+
|
112
|
+
For specified associations, the serializer will look up the association and
|
113
|
+
then serialize each element of the association. For instance, a `has_many
|
114
|
+
:comments` association will create a new `CommentSerializer` for each comment
|
115
|
+
and use it to serialize the comment.
|
116
|
+
|
117
|
+
By default, serializers simply look up the association on the original object.
|
118
|
+
You can customize this behavior by implementing a method with the name of the
|
119
|
+
association and returning a different Array. Often, you will do this to
|
120
|
+
customize the objects returned based on the current user.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class PostSerializer < ActiveModel::Serializer
|
124
|
+
attributes :id, :title, :body
|
125
|
+
has_many :comments
|
126
|
+
|
127
|
+
# only let the user see comments he created.
|
128
|
+
def comments
|
129
|
+
post.comments.where(:created_by => @scope)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
In a serializer, `@scope` is the current authorization scope (usually
|
135
|
+
`current_user`), which the controller gives to the serializer when you call
|
136
|
+
`render :json`
|
137
|
+
|
138
|
+
As with attributes, you can also change the JSON key that the serializer should
|
139
|
+
use for a particular association.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
class PostSerializer < ActiveModel::Serializer
|
143
|
+
attributes :id, :title, :body
|
144
|
+
|
145
|
+
# look up comments, but use +my_comments+ as the key in JSON
|
146
|
+
has_many :comments, :key => :my_comments
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
## Embedding Associations
|
151
|
+
|
152
|
+
By default, associations will be embedded inside the serialized object. So if
|
153
|
+
you have a post, the outputted JSON will look like:
|
154
|
+
|
155
|
+
```json
|
156
|
+
{
|
157
|
+
"post": {
|
158
|
+
"id": 1,
|
159
|
+
"title": "New post",
|
160
|
+
"body": "A body!",
|
161
|
+
"comments": [
|
162
|
+
{ "id": 1, "body": "what a dumb post" }
|
163
|
+
]
|
164
|
+
}
|
165
|
+
}
|
166
|
+
```
|
167
|
+
|
168
|
+
This is convenient for simple use-cases, but for more complex clients, it is
|
169
|
+
better to supply an Array of IDs for the association. This makes your API more
|
170
|
+
flexible from a performance standpoint and avoids wasteful duplication.
|
171
|
+
|
172
|
+
To embed IDs instead of associations, simply use the `embed` class method:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
class PostSerializer < ActiveModel::Serializer
|
176
|
+
embed :ids
|
177
|
+
|
178
|
+
attributes :id, :title, :body
|
179
|
+
has_many :comments
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
Now, any associations will be supplied as an Array of IDs:
|
184
|
+
|
185
|
+
```json
|
186
|
+
{
|
187
|
+
"post": {
|
188
|
+
"id": 1,
|
189
|
+
"title": "New post",
|
190
|
+
"body": "A body!",
|
191
|
+
"comments": [ 1, 2, 3 ]
|
192
|
+
}
|
193
|
+
}
|
194
|
+
```
|
195
|
+
|
196
|
+
In addition to supplying an Array of IDs, you may want to side-load the data
|
197
|
+
alongside the main object. This makes it easier to process the entire package
|
198
|
+
of data without having to recursively scan the tree looking for embedded
|
199
|
+
information. It also ensures that associations that are shared between several
|
200
|
+
objects (like tags), are only delivered once for the entire payload.
|
201
|
+
|
202
|
+
You can specify that the data be included like this:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class PostSerializer < ActiveModel::Serializer
|
206
|
+
embed :ids, :include => true
|
207
|
+
|
208
|
+
attributes :id, :title, :body
|
209
|
+
has_many :comments
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
Assuming that the comments also `has_many :tags`, you will get a JSON like
|
214
|
+
this:
|
215
|
+
|
216
|
+
```json
|
217
|
+
{
|
218
|
+
"post": {
|
219
|
+
"id": 1,
|
220
|
+
"title": "New post",
|
221
|
+
"body": "A body!",
|
222
|
+
"comments": [ 1 ]
|
223
|
+
},
|
224
|
+
"comments": [
|
225
|
+
{ "id": 1, "body": "what a dumb post", "tags": [ 1, 2 ] },
|
226
|
+
{ "id": 1, "body": "i liked it", "tags": [ 1, 3 ] },
|
227
|
+
],
|
228
|
+
"tags": [
|
229
|
+
{ "id": 1, "name": "short" },
|
230
|
+
{ "id": 2, "name": "whiny" },
|
231
|
+
{ "id": 3, "name": "happy" }
|
232
|
+
]
|
233
|
+
}
|
234
|
+
```
|
235
|
+
|
236
|
+
You can also specify a different root for the embedded objects than the key
|
237
|
+
used to reference them:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
class PostSerializer < ActiveModel::Serializer
|
241
|
+
embed :ids, :include => true
|
242
|
+
|
243
|
+
attributes :id, :title, :body
|
244
|
+
has_many :comments, :key => :comment_ids, :root => :comment_objects
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
This would generate JSON that would look like this:
|
249
|
+
|
250
|
+
```json
|
251
|
+
{
|
252
|
+
"post": {
|
253
|
+
"id": 1,
|
254
|
+
"title": "New post",
|
255
|
+
"body": "A body!",
|
256
|
+
"comment_ids": [ 1 ]
|
257
|
+
},
|
258
|
+
"comment_objects": [
|
259
|
+
{ "id": 1, "body": "what a dumb post" }
|
260
|
+
]
|
261
|
+
}
|
262
|
+
```
|
263
|
+
|
264
|
+
**NOTE**: The `embed :ids` mechanism is primary useful for clients that process
|
265
|
+
data in bulk and load it into a local store. For these clients, the ability to
|
266
|
+
easily see all of the data per type, rather than having to recursively scan the
|
267
|
+
data looking for information, is extremely useful.
|
268
|
+
|
269
|
+
If you are mostly working with the data in simple scenarios and manually making
|
270
|
+
Ajax requests, you probably just want to use the default embedded behavior.
|
data/RELEASE_NOTES.md
ADDED
@@ -1,4 +1,8 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
4
|
+
require "active_model/serializers/version"
|
5
|
+
|
2
6
|
Gem::Specification.new do |gem|
|
3
7
|
gem.authors = ["José Valim", "Yehuda Katz"]
|
4
8
|
gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"]
|
@@ -11,7 +15,7 @@ Gem::Specification.new do |gem|
|
|
11
15
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
12
16
|
gem.name = "active_model_serializers"
|
13
17
|
gem.require_paths = ["lib"]
|
14
|
-
gem.version =
|
18
|
+
gem.version = ActiveModel::Serializer::VERSION
|
15
19
|
|
16
20
|
gem.add_dependency 'activemodel', '~> 3.0'
|
17
21
|
gem.add_development_dependency "rails", "~> 3.0"
|
@@ -33,12 +33,23 @@ module ActionController
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def serialization_scope
|
36
|
-
send(_serialization_scope)
|
36
|
+
send(_serialization_scope) if respond_to?(_serialization_scope)
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_serializer_options
|
37
40
|
end
|
38
41
|
|
39
42
|
def _render_option_json(json, options)
|
40
|
-
if json.respond_to?(:
|
41
|
-
|
43
|
+
if json.respond_to?(:to_ary)
|
44
|
+
options[:root] ||= controller_name
|
45
|
+
end
|
46
|
+
|
47
|
+
serializer = options.delete(:serializer) ||
|
48
|
+
(json.respond_to?(:active_model_serializer) && json.active_model_serializer)
|
49
|
+
|
50
|
+
if serializer
|
51
|
+
options[:scope] = serialization_scope
|
52
|
+
json = serializer.new(json, options.merge(default_serializer_options || {}))
|
42
53
|
end
|
43
54
|
super
|
44
55
|
end
|
@@ -1,23 +1,47 @@
|
|
1
1
|
require "active_support/core_ext/class/attribute"
|
2
2
|
require "active_support/core_ext/module/anonymous"
|
3
|
+
require "set"
|
3
4
|
|
4
5
|
module ActiveModel
|
6
|
+
class OrderedSet
|
7
|
+
def initialize(array)
|
8
|
+
@array = array
|
9
|
+
@hash = {}
|
10
|
+
|
11
|
+
array.each do |item|
|
12
|
+
@hash[item] = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge!(other)
|
17
|
+
other.each do |item|
|
18
|
+
next if @hash.key?(item)
|
19
|
+
|
20
|
+
@hash[item] = true
|
21
|
+
@array.push item
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_a
|
26
|
+
@array
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
5
30
|
# Active Model Array Serializer
|
6
31
|
#
|
7
32
|
# It serializes an array checking if each element that implements
|
8
|
-
# the +active_model_serializer+ method
|
33
|
+
# the +active_model_serializer+ method.
|
9
34
|
class ArraySerializer
|
10
|
-
attr_reader :object, :
|
35
|
+
attr_reader :object, :options
|
11
36
|
|
12
|
-
def initialize(object,
|
13
|
-
@object, @
|
14
|
-
@hash = options[:hash]
|
37
|
+
def initialize(object, options={})
|
38
|
+
@object, @options = object, options
|
15
39
|
end
|
16
40
|
|
17
41
|
def serializable_array
|
18
42
|
@object.map do |item|
|
19
43
|
if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer)
|
20
|
-
serializer.new(item,
|
44
|
+
serializer.new(item, @options)
|
21
45
|
else
|
22
46
|
item
|
23
47
|
end
|
@@ -25,11 +49,13 @@ module ActiveModel
|
|
25
49
|
end
|
26
50
|
|
27
51
|
def as_json(*args)
|
28
|
-
@hash = {}
|
29
|
-
|
52
|
+
@options[:hash] = hash = {}
|
53
|
+
@options[:unique_values] = {}
|
54
|
+
|
55
|
+
array = serializable_array.map(&:serializable_hash)
|
30
56
|
|
31
57
|
if root = @options[:root]
|
32
|
-
|
58
|
+
hash.merge!(root => array)
|
33
59
|
else
|
34
60
|
array
|
35
61
|
end
|
@@ -40,12 +66,13 @@ module ActiveModel
|
|
40
66
|
#
|
41
67
|
# Provides a basic serializer implementation that allows you to easily
|
42
68
|
# control how a given object is going to be serialized. On initialization,
|
43
|
-
# it expects to object as arguments, a resource and
|
69
|
+
# it expects to object as arguments, a resource and options. For example,
|
44
70
|
# one may do in a controller:
|
45
71
|
#
|
46
|
-
# PostSerializer.new(@post, current_user).to_json
|
72
|
+
# PostSerializer.new(@post, :scope => current_user).to_json
|
47
73
|
#
|
48
|
-
# The object to be serialized is the +@post+ and the
|
74
|
+
# The object to be serialized is the +@post+ and the current user is passed
|
75
|
+
# in for authorization purposes.
|
49
76
|
#
|
50
77
|
# We use the scope to check if a given attribute should be serialized or not.
|
51
78
|
# For example, some attributes maybe only be returned if +current_user+ is the
|
@@ -70,22 +97,86 @@ module ActiveModel
|
|
70
97
|
#
|
71
98
|
class Serializer
|
72
99
|
module Associations #:nodoc:
|
73
|
-
class Config
|
74
|
-
|
75
|
-
|
100
|
+
class Config #:nodoc:
|
101
|
+
class_attribute :options
|
102
|
+
|
103
|
+
def self.refine(name, class_options)
|
104
|
+
current_class = self
|
105
|
+
|
106
|
+
Class.new(self) do
|
107
|
+
singleton_class.class_eval do
|
108
|
+
define_method(:to_s) do
|
109
|
+
"(subclass of #{current_class.name})"
|
110
|
+
end
|
111
|
+
|
112
|
+
alias inspect to_s
|
113
|
+
end
|
114
|
+
|
115
|
+
self.options = class_options
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
self.options = {}
|
120
|
+
|
121
|
+
def initialize(name, source, options={})
|
122
|
+
@name = name
|
123
|
+
@source = source
|
124
|
+
@options = options
|
125
|
+
end
|
126
|
+
|
127
|
+
def option(key, default=nil)
|
128
|
+
if @options.key?(key)
|
129
|
+
@options[key]
|
130
|
+
elsif self.class.options.key?(key)
|
131
|
+
self.class.options[key]
|
132
|
+
else
|
133
|
+
default
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def target_serializer
|
138
|
+
option(:serializer)
|
139
|
+
end
|
140
|
+
|
141
|
+
def source_serializer
|
142
|
+
@source
|
76
143
|
end
|
77
144
|
|
78
145
|
def key
|
79
|
-
|
146
|
+
option(:key) || @name
|
147
|
+
end
|
148
|
+
|
149
|
+
def root
|
150
|
+
option(:root) || plural_key
|
151
|
+
end
|
152
|
+
|
153
|
+
def name
|
154
|
+
option(:name) || @name
|
155
|
+
end
|
156
|
+
|
157
|
+
def associated_object
|
158
|
+
option(:value) || source_serializer.send(name)
|
159
|
+
end
|
160
|
+
|
161
|
+
def embed_ids?
|
162
|
+
option(:embed, source_serializer._embed) == :ids
|
163
|
+
end
|
164
|
+
|
165
|
+
def embed_objects?
|
166
|
+
option(:embed, source_serializer._embed) == :objects
|
167
|
+
end
|
168
|
+
|
169
|
+
def embed_in_root?
|
170
|
+
option(:include, source_serializer._root_embed)
|
80
171
|
end
|
81
172
|
|
82
|
-
|
173
|
+
protected
|
83
174
|
|
84
|
-
def find_serializable(object
|
85
|
-
if
|
86
|
-
|
175
|
+
def find_serializable(object)
|
176
|
+
if target_serializer
|
177
|
+
target_serializer.new(object, source_serializer.options)
|
87
178
|
elsif object.respond_to?(:active_model_serializer) && (ams = object.active_model_serializer)
|
88
|
-
ams.new(object,
|
179
|
+
ams.new(object, source_serializer.options)
|
89
180
|
else
|
90
181
|
object
|
91
182
|
end
|
@@ -93,32 +184,47 @@ module ActiveModel
|
|
93
184
|
end
|
94
185
|
|
95
186
|
class HasMany < Config #:nodoc:
|
96
|
-
|
97
|
-
|
98
|
-
|
187
|
+
alias plural_key key
|
188
|
+
|
189
|
+
def serialize
|
190
|
+
associated_object.map do |item|
|
191
|
+
find_serializable(item).serializable_hash
|
99
192
|
end
|
100
|
-
{ key => array }
|
101
193
|
end
|
194
|
+
alias serialize_many serialize
|
102
195
|
|
103
|
-
def serialize_ids
|
196
|
+
def serialize_ids
|
104
197
|
# Use pluck or select_columns if available
|
105
198
|
# return collection.ids if collection.respond_to?(:ids)
|
106
199
|
|
107
|
-
|
200
|
+
associated_object.map do |item|
|
108
201
|
item.read_attribute_for_serialization(:id)
|
109
202
|
end
|
110
|
-
|
111
|
-
{ key => array }
|
112
203
|
end
|
113
204
|
end
|
114
205
|
|
115
206
|
class HasOne < Config #:nodoc:
|
116
|
-
def
|
117
|
-
|
207
|
+
def plural_key
|
208
|
+
key.to_s.pluralize.to_sym
|
209
|
+
end
|
210
|
+
|
211
|
+
def serialize
|
212
|
+
object = associated_object
|
213
|
+
object && find_serializable(object).serializable_hash
|
118
214
|
end
|
119
215
|
|
120
|
-
def
|
121
|
-
|
216
|
+
def serialize_many
|
217
|
+
object = associated_object
|
218
|
+
value = object && find_serializable(object).serializable_hash
|
219
|
+
value ? [value] : []
|
220
|
+
end
|
221
|
+
|
222
|
+
def serialize_ids
|
223
|
+
if object = associated_object
|
224
|
+
object.read_attribute_for_serialization(:id)
|
225
|
+
else
|
226
|
+
nil
|
227
|
+
end
|
122
228
|
end
|
123
229
|
end
|
124
230
|
end
|
@@ -127,7 +233,7 @@ module ActiveModel
|
|
127
233
|
self._attributes = {}
|
128
234
|
|
129
235
|
class_attribute :_associations
|
130
|
-
self._associations =
|
236
|
+
self._associations = {}
|
131
237
|
|
132
238
|
class_attribute :_root
|
133
239
|
class_attribute :_embed
|
@@ -150,11 +256,14 @@ module ActiveModel
|
|
150
256
|
|
151
257
|
def associate(klass, attrs) #:nodoc:
|
152
258
|
options = attrs.extract_options!
|
153
|
-
self._associations
|
259
|
+
self._associations = _associations.dup
|
260
|
+
|
261
|
+
attrs.each do |attr|
|
154
262
|
unless method_defined?(attr)
|
155
263
|
class_eval "def #{attr}() object.#{attr} end", __FILE__, __LINE__
|
156
264
|
end
|
157
|
-
|
265
|
+
|
266
|
+
self._associations[attr] = klass.refine(attr, options)
|
158
267
|
end
|
159
268
|
end
|
160
269
|
|
@@ -188,7 +297,6 @@ module ActiveModel
|
|
188
297
|
# { :name => :string, :age => :integer }
|
189
298
|
#
|
190
299
|
# The +associations+ hash looks like this:
|
191
|
-
#
|
192
300
|
# { :posts => { :has_many => :posts } }
|
193
301
|
#
|
194
302
|
# If :key is used:
|
@@ -220,7 +328,9 @@ module ActiveModel
|
|
220
328
|
hash.merge key => column.type
|
221
329
|
end
|
222
330
|
|
223
|
-
associations = _associations.inject({}) do |hash,
|
331
|
+
associations = _associations.inject({}) do |hash, (attr,association_class)|
|
332
|
+
association = association_class.new(attr, self)
|
333
|
+
|
224
334
|
model_association = klass.reflect_on_association(association.name)
|
225
335
|
hash.merge association.key => { model_association.macro => model_association.name }
|
226
336
|
end
|
@@ -260,11 +370,10 @@ module ActiveModel
|
|
260
370
|
end
|
261
371
|
end
|
262
372
|
|
263
|
-
attr_reader :object, :
|
373
|
+
attr_reader :object, :options
|
264
374
|
|
265
|
-
def initialize(object,
|
266
|
-
@object, @
|
267
|
-
@hash = options[:hash]
|
375
|
+
def initialize(object, options={})
|
376
|
+
@object, @options = object, options
|
268
377
|
end
|
269
378
|
|
270
379
|
# Returns a json representation of the serializable
|
@@ -272,63 +381,107 @@ module ActiveModel
|
|
272
381
|
def as_json(options=nil)
|
273
382
|
options ||= {}
|
274
383
|
if root = options.fetch(:root, @options.fetch(:root, _root))
|
275
|
-
@hash = hash = {}
|
384
|
+
@options[:hash] = hash = {}
|
385
|
+
@options[:unique_values] = {}
|
386
|
+
|
276
387
|
hash.merge!(root => serializable_hash)
|
277
388
|
hash
|
278
389
|
else
|
279
|
-
|
390
|
+
serializable_hash
|
280
391
|
end
|
281
392
|
end
|
282
393
|
|
283
394
|
# Returns a hash representation of the serializable
|
284
395
|
# object without the root.
|
285
396
|
def serializable_hash
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
elsif _embed == :objects
|
290
|
-
attributes.merge(associations)
|
291
|
-
else
|
292
|
-
attributes
|
293
|
-
end
|
397
|
+
node = attributes
|
398
|
+
include_associations!(node) if _embed
|
399
|
+
node
|
294
400
|
end
|
295
401
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
if
|
301
|
-
|
302
|
-
elsif value
|
303
|
-
hash[key] = value
|
402
|
+
def include_associations!(node)
|
403
|
+
_associations.each do |attr, klass|
|
404
|
+
opts = { :node => node }
|
405
|
+
|
406
|
+
if options.include?(:include) || options.include?(:exclude)
|
407
|
+
opts[:include] = included_association?(attr)
|
304
408
|
end
|
409
|
+
|
410
|
+
include! attr, opts
|
305
411
|
end
|
306
412
|
end
|
307
413
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
hash.merge! association.serialize(associated_object, scope, self, :hash => @hash)
|
414
|
+
def included_association?(name)
|
415
|
+
if options.key?(:include)
|
416
|
+
options[:include].include?(name)
|
417
|
+
elsif options.key?(:exclude)
|
418
|
+
!options[:exclude].include?(name)
|
419
|
+
else
|
420
|
+
true
|
316
421
|
end
|
317
|
-
|
318
|
-
hash
|
319
422
|
end
|
320
423
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
hash
|
424
|
+
def include!(name, options={})
|
425
|
+
# Make sure that if a special options[:hash] was passed in, we generate
|
426
|
+
# a new unique values hash and don't clobber the original. If the hash
|
427
|
+
# passed in is the same as the current options hash, use the current
|
428
|
+
# unique values.
|
429
|
+
#
|
430
|
+
# TODO: Should passing in a Hash even be public API here?
|
431
|
+
unique_values =
|
432
|
+
if hash = options[:hash]
|
433
|
+
if @options[:hash] == hash
|
434
|
+
@options[:unique_values] ||= {}
|
435
|
+
else
|
436
|
+
{}
|
437
|
+
end
|
438
|
+
else
|
439
|
+
hash = @options[:hash]
|
440
|
+
@options[:unique_values] ||= {}
|
441
|
+
end
|
325
442
|
|
326
|
-
|
327
|
-
|
328
|
-
|
443
|
+
node = options[:node]
|
444
|
+
value = options[:value]
|
445
|
+
|
446
|
+
association_class =
|
447
|
+
if klass = _associations[name]
|
448
|
+
klass
|
449
|
+
elsif value.respond_to?(:to_ary)
|
450
|
+
Associations::HasMany
|
451
|
+
else
|
452
|
+
Associations::HasOne
|
453
|
+
end
|
454
|
+
|
455
|
+
association = association_class.new(name, self, options)
|
456
|
+
|
457
|
+
if association.embed_ids?
|
458
|
+
node[association.key] = association.serialize_ids
|
459
|
+
|
460
|
+
if association.embed_in_root?
|
461
|
+
merge_association hash, association.root, association.serialize_many, unique_values
|
462
|
+
end
|
463
|
+
elsif association.embed_objects?
|
464
|
+
node[association.key] = association.serialize
|
329
465
|
end
|
466
|
+
end
|
330
467
|
|
331
|
-
|
468
|
+
# In some cases, an Array of associations is built by merging the associated
|
469
|
+
# content for all of the children. For instance, if a Post has_many comments,
|
470
|
+
# which has_many tags, the top-level :tags key will contain the merged list
|
471
|
+
# of all tags for all comments of the post.
|
472
|
+
#
|
473
|
+
# In order to make this efficient, we store a :unique_values hash containing
|
474
|
+
# a unique list of all of the objects that are already in the Array. This
|
475
|
+
# avoids the need to scan through the Array looking for entries every time
|
476
|
+
# we want to merge a new list of values.
|
477
|
+
def merge_association(hash, key, value, unique_values)
|
478
|
+
if current_value = unique_values[key]
|
479
|
+
current_value.merge! value
|
480
|
+
hash[key] = current_value.to_a
|
481
|
+
elsif value
|
482
|
+
hash[key] = value
|
483
|
+
unique_values[key] = OrderedSet.new(value)
|
484
|
+
end
|
332
485
|
end
|
333
486
|
|
334
487
|
# Returns a hash representation of the serializable
|