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.
@@ -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
@@ -1,7 +1,4 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in active_model_serializers.gemspec
3
+ # Specify gem dependencies in active_model_serializers.gemspec
4
4
  gemspec
5
-
6
- gem "pry"
7
- gem "simplecov", :require => false
@@ -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 `serializer generator`:
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 `render_with`, which uses `to_json` under the hood. Also
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, the serializer will look up the attribute on the
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 => options[:scope])
283
+ post.comments.where(:created_by => scope)
130
284
  end
131
285
  end
132
286
  ```
133
287
 
134
- In a serializer, `options[: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
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
+ ```
@@ -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', '~> 3.0'
21
- gem.add_development_dependency "rails", "~> 3.0"
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
@@ -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