active_model_serializers 0.5.2 → 0.6.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/.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
|