active_model_serializers 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +23 -1
- data/Gemfile +1 -4
- data/{README.markdown → README.md} +237 -9
- data/RELEASE_NOTES.md +11 -0
- data/active_model_serializers.gemspec +5 -3
- data/cruft.md +19 -0
- data/gemfiles/Gemfile.edge-rails +9 -0
- data/lib/action_controller/serialization.rb +10 -6
- data/lib/active_model/array_serializer.rb +58 -0
- data/lib/active_model/ordered_set.rb +25 -0
- data/lib/active_model/serializer.rb +113 -110
- data/lib/active_model/serializers/version.rb +1 -1
- data/lib/active_model_serializers.rb +17 -0
- data/test/no_serialization_scope_test.rb +34 -0
- data/test/serialization_test.rb +56 -2
- data/test/serializer_support_test.rb +20 -1
- data/test/serializer_test.rb +503 -5
- data/test/test_helper.rb +2 -2
- metadata +35 -16
data/.travis.yml
CHANGED
@@ -1,7 +1,29 @@
|
|
1
|
+
language: ruby
|
2
|
+
before_install:
|
3
|
+
- gem install bundler
|
1
4
|
rvm:
|
2
5
|
- 1.8.7
|
3
6
|
- 1.9.2
|
4
7
|
- 1.9.3
|
5
8
|
- ree
|
6
9
|
- jruby
|
7
|
-
- rbx
|
10
|
+
- rbx
|
11
|
+
gemfile:
|
12
|
+
- Gemfile
|
13
|
+
- gemfiles/Gemfile.edge-rails
|
14
|
+
matrix:
|
15
|
+
exclude:
|
16
|
+
# Edge Rails is only compatible with 1.9.3
|
17
|
+
- gemfile: gemfiles/Gemfile.edge-rails
|
18
|
+
rvm: 1.8.7
|
19
|
+
- gemfile: gemfiles/Gemfile.edge-rails
|
20
|
+
rvm: 1.9.2
|
21
|
+
- gemfile: gemfiles/Gemfile.edge-rails
|
22
|
+
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
|
data/Gemfile
CHANGED
@@ -39,7 +39,7 @@ $ rails g resource post title:string body:string
|
|
39
39
|
|
40
40
|
This will generate a serializer in `app/serializers/post_serializer.rb` for
|
41
41
|
your new model. You can also generate a serializer for an existing model with
|
42
|
-
the
|
42
|
+
the serializer generator:
|
43
43
|
|
44
44
|
```
|
45
45
|
$ rails g serializer post
|
@@ -66,10 +66,108 @@ end
|
|
66
66
|
In this case, Rails will look for a serializer named `PostSerializer`, and if
|
67
67
|
it exists, use it to serialize the `Post`.
|
68
68
|
|
69
|
-
This also works with `
|
69
|
+
This also works with `respond_with`, which uses `to_json` under the hood. Also
|
70
70
|
note that any options passed to `render :json` will be passed to your
|
71
71
|
serializer and available as `@options` inside.
|
72
72
|
|
73
|
+
To specify a custom serializer for an object, there are 2 options:
|
74
|
+
|
75
|
+
#### 1. Specify the serializer in your model:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class Post < ActiveRecord::Base
|
79
|
+
def active_model_serializer
|
80
|
+
FancyPostSerializer
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
#### 2. Specify the serializer when you render the object:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
render :json => @post, :serializer => FancyPostSerializer
|
89
|
+
```
|
90
|
+
|
91
|
+
## Arrays
|
92
|
+
|
93
|
+
In your controllers, when you use `render :json` for an array of objects, AMS will
|
94
|
+
use `ActiveModel::ArraySerializer` (included in this project) as the base serializer,
|
95
|
+
and the individual `Serializer` for the objects contained in that array.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
class PostSerializer < ActiveModel::Serializer
|
99
|
+
attributes :title, :body
|
100
|
+
end
|
101
|
+
|
102
|
+
class PostsController < ApplicationController
|
103
|
+
def index
|
104
|
+
@posts = Post.all
|
105
|
+
render :json => @posts
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
Given the example above, the index action will return
|
111
|
+
|
112
|
+
```json
|
113
|
+
{
|
114
|
+
"posts":
|
115
|
+
[
|
116
|
+
{ "title": "Post 1", "body": "Hello!" },
|
117
|
+
{ "title": "Post 2", "body": "Goodbye!" }
|
118
|
+
]
|
119
|
+
}
|
120
|
+
```
|
121
|
+
|
122
|
+
By default, the root element is the name of the controller. For example, `PostsController`
|
123
|
+
generates a root element "posts". To change it:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
render :json => @posts, :root => "some_posts"
|
127
|
+
```
|
128
|
+
|
129
|
+
You may disable the root element for arrays at the top level, which will result in
|
130
|
+
more concise json. To disable the root element for arrays, you have 3 options:
|
131
|
+
|
132
|
+
#### 1. Disable root globally for in `ArraySerializer`. In an initializer:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
ActiveModel::ArraySerializer.root = false
|
136
|
+
```
|
137
|
+
|
138
|
+
#### 2. Disable root per render call in your controller:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
render :json => @posts, :root => false
|
142
|
+
```
|
143
|
+
|
144
|
+
#### 3. Create a custom `ArraySerializer` and render arrays with it:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
class CustomArraySerializer < ActiveModel::ArraySerializer
|
148
|
+
self.root = false
|
149
|
+
end
|
150
|
+
|
151
|
+
# controller:
|
152
|
+
render :json => @posts, :serializer => CustomArraySerializer
|
153
|
+
```
|
154
|
+
|
155
|
+
Disabling the root element of the array with any of the above 3 methods
|
156
|
+
will produce
|
157
|
+
|
158
|
+
```json
|
159
|
+
[
|
160
|
+
{ "title": "Post 1", "body": "Hello!" },
|
161
|
+
{ "title": "Post 2", "body": "Goodbye!" }
|
162
|
+
]
|
163
|
+
```
|
164
|
+
|
165
|
+
To specify a custom serializer for the items within an array:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
render :json => @posts, :each_serializer => FancyPostSerializer
|
169
|
+
```
|
170
|
+
|
73
171
|
## Getting the old version
|
74
172
|
|
75
173
|
If you find that your project is already relying on the old rails to_json
|
@@ -89,11 +187,49 @@ end
|
|
89
187
|
|
90
188
|
## Attributes
|
91
189
|
|
92
|
-
For specified attributes,
|
190
|
+
For specified attributes, a serializer will look up the attribute on the
|
93
191
|
object you passed to `render :json`. It uses
|
94
192
|
`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a
|
95
193
|
regular attribute lookup.
|
96
194
|
|
195
|
+
Before looking up the attribute on the object, a serializer will check for the
|
196
|
+
presence of a method with the name of the attribute. This allows serializers to
|
197
|
+
include properties beyond the simple attributes of the model. For example:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class PersonSerializer < ActiveModel::Serializer
|
201
|
+
attributes :first_name, :last_name, :full_name
|
202
|
+
|
203
|
+
def full_name
|
204
|
+
"#{object.first_name} #{object.last_name}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
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`).
|
212
|
+
|
213
|
+
You can also access the `scope` method, which provides an
|
214
|
+
authorization context to your serializer. By default, scope
|
215
|
+
is the current user of your application, but this
|
216
|
+
[can be customized](#customizing-scope).
|
217
|
+
|
218
|
+
Serializers will check for the presence of a method named
|
219
|
+
`include_[ATTRIBUTE]?` to determine whether a particular attribute should be
|
220
|
+
included in the output. This is typically used to customize output
|
221
|
+
based on `scope`. For example:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
class PostSerializer < ActiveModel::Serializer
|
225
|
+
attributes :id, :title, :body, :author
|
226
|
+
|
227
|
+
def include_author?
|
228
|
+
scope.admin?
|
229
|
+
end
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
97
233
|
If you would like the key in the outputted JSON to be different from its name
|
98
234
|
in ActiveRecord, you can use the `:key` option to customize it:
|
99
235
|
|
@@ -107,6 +243,24 @@ class PostSerializer < ActiveModel::Serializer
|
|
107
243
|
end
|
108
244
|
```
|
109
245
|
|
246
|
+
If you would like direct, low-level control of attribute serialization, you can
|
247
|
+
completely override the `attributes` method to return the hash you need:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
class PersonSerializer < ActiveModel::Serializer
|
251
|
+
attributes :first_name, :last_name
|
252
|
+
|
253
|
+
def attributes
|
254
|
+
hash = super
|
255
|
+
if scope.admin?
|
256
|
+
hash["ssn"] = object.ssn
|
257
|
+
hash["secret"] = object.mothers_maiden_name
|
258
|
+
end
|
259
|
+
hash
|
260
|
+
end
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
110
264
|
## Associations
|
111
265
|
|
112
266
|
For specified associations, the serializer will look up the association and
|
@@ -126,16 +280,12 @@ class PostSerializer < ActiveModel::Serializer
|
|
126
280
|
|
127
281
|
# only let the user see comments he created.
|
128
282
|
def comments
|
129
|
-
post.comments.where(:created_by =>
|
283
|
+
post.comments.where(:created_by => scope)
|
130
284
|
end
|
131
285
|
end
|
132
286
|
```
|
133
287
|
|
134
|
-
|
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
|
288
|
+
As with attributes, you can change the JSON key that the serializer should
|
139
289
|
use for a particular association.
|
140
290
|
|
141
291
|
```ruby
|
@@ -147,6 +297,44 @@ class PostSerializer < ActiveModel::Serializer
|
|
147
297
|
end
|
148
298
|
```
|
149
299
|
|
300
|
+
Also, as with attributes, serializers will check for the presence
|
301
|
+
of a method named `include_[ASSOCIATION]?` to determine whether a particular association
|
302
|
+
should be included in the output. For example:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
class PostSerializer < ActiveModel::Serializer
|
306
|
+
attributes :id, :title, :body
|
307
|
+
has_many :comments
|
308
|
+
|
309
|
+
def include_comments?
|
310
|
+
!post.comments_disabled?
|
311
|
+
end
|
312
|
+
end
|
313
|
+
```
|
314
|
+
|
315
|
+
If you would like lower-level control of association serialization, you can
|
316
|
+
override `include_associations!` to specify which associations should be included:
|
317
|
+
|
318
|
+
```ruby
|
319
|
+
class PostSerializer < ActiveModel::Serializer
|
320
|
+
attributes :id, :title, :body
|
321
|
+
has_one :author
|
322
|
+
has_many :comments
|
323
|
+
|
324
|
+
def include_associations!
|
325
|
+
include! :author if scope.admin?
|
326
|
+
include! :comments unless object.comments_disabled?
|
327
|
+
end
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
You may also use the `:serializer` option to specify a custom serializer class and the `:polymorphic` option to specify an association that is polymorphic (STI), e.g.:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
has_many :comments, :serializer => CommentShortSerializer
|
335
|
+
has_one :reviewer, :polymorphic => true
|
336
|
+
```
|
337
|
+
|
150
338
|
## Embedding Associations
|
151
339
|
|
152
340
|
By default, associations will be embedded inside the serialized object. So if
|
@@ -193,6 +381,33 @@ Now, any associations will be supplied as an Array of IDs:
|
|
193
381
|
}
|
194
382
|
```
|
195
383
|
|
384
|
+
Alternatively, you can choose to embed only the ids or the associated objects per association:
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
class PostSerializer < ActiveModel::Serializer
|
388
|
+
attributes :id, :title, :body
|
389
|
+
|
390
|
+
has_many :comments, embed: :objects
|
391
|
+
has_many :tags, embed: :ids
|
392
|
+
end
|
393
|
+
```
|
394
|
+
|
395
|
+
The JSON will look like this:
|
396
|
+
|
397
|
+
```json
|
398
|
+
{
|
399
|
+
"post": {
|
400
|
+
"id": 1,
|
401
|
+
"title": "New post",
|
402
|
+
"body": "A body!",
|
403
|
+
"comments": [
|
404
|
+
{ "id": 1, "body": "what a dumb post" }
|
405
|
+
],
|
406
|
+
"tags": [ 1, 2, 3 ]
|
407
|
+
}
|
408
|
+
}
|
409
|
+
```
|
410
|
+
|
196
411
|
In addition to supplying an Array of IDs, you may want to side-load the data
|
197
412
|
alongside the main object. This makes it easier to process the entire package
|
198
413
|
of data without having to recursively scan the tree looking for embedded
|
@@ -268,3 +483,16 @@ data looking for information, is extremely useful.
|
|
268
483
|
|
269
484
|
If you are mostly working with the data in simple scenarios and manually making
|
270
485
|
Ajax requests, you probably just want to use the default embedded behavior.
|
486
|
+
|
487
|
+
## Customizing Scope
|
488
|
+
|
489
|
+
In a serializer, `scope` is the current authorization scope which the controller
|
490
|
+
provides to the serializer when you call `render :json`. By default, this is
|
491
|
+
`current_user`, but can be customized in your controller by calling
|
492
|
+
`serialization_scope`:
|
493
|
+
|
494
|
+
```ruby
|
495
|
+
class ApplicationController < ActionController::Base
|
496
|
+
serialization_scope :current_admin
|
497
|
+
end
|
498
|
+
```
|
data/RELEASE_NOTES.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# VERSION 0.6 (Oct 22, 2012)
|
2
|
+
|
3
|
+
* Serialize sets properly
|
4
|
+
* Add root option to ArraySerializer
|
5
|
+
* Support polymorphic associations
|
6
|
+
* Support :each_serializer in ArraySerializer
|
7
|
+
* Add `scope` method to easily access the scope in the serializer
|
8
|
+
* Fix regression with Rails 3.2.6; add Rails 4 support
|
9
|
+
* Allow serialization_scope to be disabled with serialization_scope nil
|
10
|
+
* Array serializer should support pure ruby objects besides serializers
|
11
|
+
|
1
12
|
# VERSION 0.5 (May 16, 2012)
|
2
13
|
|
3
14
|
* First tagged version
|
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"]
|
9
9
|
gem.description = %q{Making it easy to serialize models for client-side use}
|
10
10
|
gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!}
|
11
|
-
gem.homepage = ""
|
11
|
+
gem.homepage = "https://github.com/josevalim/active_model_serializers"
|
12
12
|
|
13
13
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
14
|
gem.files = `git ls-files`.split("\n")
|
@@ -17,6 +17,8 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
gem.version = ActiveModel::Serializer::VERSION
|
19
19
|
|
20
|
-
gem.add_dependency 'activemodel', '
|
21
|
-
gem.add_development_dependency "rails", "
|
20
|
+
gem.add_dependency 'activemodel', '>= 3.0'
|
21
|
+
gem.add_development_dependency "rails", ">= 3.0"
|
22
|
+
gem.add_development_dependency "pry"
|
23
|
+
gem.add_development_dependency "simplecov"
|
22
24
|
end
|
data/cruft.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
As of Ruby 1.9.3, it is impossible to dynamically generate a Symbol
|
2
|
+
through interpolation without generating garbage. Theoretically, Ruby
|
3
|
+
should be able to take care of this by building up the String in C and
|
4
|
+
interning the C String.
|
5
|
+
|
6
|
+
Because of this, we avoid generating dynamic Symbols at runtime. For
|
7
|
+
example, instead of generating the instrumentation event dynamically, we
|
8
|
+
have a constant with a Hash of events:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
INSTRUMENT = {
|
12
|
+
:serialize => :"serialize.serializer",
|
13
|
+
:associations => :"associations.serializer"
|
14
|
+
}
|
15
|
+
```
|
16
|
+
|
17
|
+
If Ruby ever fixes this issue and avoids generating garbage with dynamic
|
18
|
+
symbols, this code can be removed.
|
19
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gemspec :path => '..'
|
4
|
+
|
5
|
+
gem 'rails', github: 'rails/rails'
|
6
|
+
|
7
|
+
# Current dependencies of edge rails
|
8
|
+
gem 'journey', github: 'rails/journey'
|
9
|
+
gem 'activerecord-deprecated_finders' , github: 'rails/activerecord-deprecated_finders'
|
@@ -33,22 +33,26 @@ module ActionController
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def serialization_scope
|
36
|
-
send(_serialization_scope) if respond_to?(_serialization_scope)
|
36
|
+
send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope)
|
37
37
|
end
|
38
38
|
|
39
39
|
def default_serializer_options
|
40
40
|
end
|
41
41
|
|
42
42
|
def _render_option_json(json, options)
|
43
|
-
if json.respond_to?(:to_ary)
|
44
|
-
options[:root] ||= controller_name unless options[:root] == false
|
45
|
-
end
|
46
|
-
|
47
43
|
serializer = options.delete(:serializer) ||
|
48
44
|
(json.respond_to?(:active_model_serializer) && json.active_model_serializer)
|
49
45
|
|
46
|
+
if json.respond_to?(:to_ary)
|
47
|
+
if options[:root] != false && serializer.root != false
|
48
|
+
# default root element for arrays is serializer's root or the controller name
|
49
|
+
# the serializer for an Array is ActiveModel::ArraySerializer
|
50
|
+
options[:root] ||= serializer.root || controller_name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
50
54
|
if serializer
|
51
|
-
options[:scope] = serialization_scope
|
55
|
+
options[:scope] = serialization_scope unless options.has_key?(:scope)
|
52
56
|
options[:url_options] = url_options
|
53
57
|
json = serializer.new(json, options.merge(default_serializer_options || {}))
|
54
58
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "active_support/core_ext/class/attribute"
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# Active Model Array Serializer
|
5
|
+
#
|
6
|
+
# It serializes an Array, checking if each element that implements
|
7
|
+
# the +active_model_serializer+ method.
|
8
|
+
#
|
9
|
+
# To disable serialization of root elements:
|
10
|
+
#
|
11
|
+
# ActiveModel::ArraySerializer.root = false
|
12
|
+
#
|
13
|
+
class ArraySerializer
|
14
|
+
attr_reader :object, :options
|
15
|
+
|
16
|
+
class_attribute :root
|
17
|
+
|
18
|
+
def initialize(object, options={})
|
19
|
+
@object, @options = object, options
|
20
|
+
end
|
21
|
+
|
22
|
+
def serializable_array
|
23
|
+
@object.map do |item|
|
24
|
+
if @options.has_key? :each_serializer
|
25
|
+
serializer = @options[:each_serializer]
|
26
|
+
elsif item.respond_to?(:active_model_serializer)
|
27
|
+
serializer = item.active_model_serializer
|
28
|
+
end
|
29
|
+
|
30
|
+
if serializer
|
31
|
+
serializer.new(item, @options)
|
32
|
+
else
|
33
|
+
item
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def as_json(*args)
|
39
|
+
@options[:hash] = hash = {}
|
40
|
+
@options[:unique_values] = {}
|
41
|
+
|
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
|
+
if root = @options[:root]
|
51
|
+
hash.merge!(root => array)
|
52
|
+
else
|
53
|
+
array
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|