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
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class OrderedSet
|
3
|
+
def initialize(array)
|
4
|
+
@array = array
|
5
|
+
@hash = {}
|
6
|
+
|
7
|
+
array.each do |item|
|
8
|
+
@hash[item] = true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def merge!(other)
|
13
|
+
other.each do |item|
|
14
|
+
next if @hash.key?(item)
|
15
|
+
|
16
|
+
@hash[item] = true
|
17
|
+
@array.push item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_a
|
22
|
+
@array
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -3,78 +3,11 @@ require "active_support/core_ext/module/anonymous"
|
|
3
3
|
require "set"
|
4
4
|
|
5
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
|
-
|
30
|
-
# Active Model Array Serializer
|
31
|
-
#
|
32
|
-
# It serializes an array checking if each element that implements
|
33
|
-
# the +active_model_serializer+ method.
|
34
|
-
class ArraySerializer
|
35
|
-
attr_reader :object, :options
|
36
|
-
|
37
|
-
def initialize(object, options={})
|
38
|
-
@object, @options = object, options
|
39
|
-
end
|
40
|
-
|
41
|
-
def serializable_array
|
42
|
-
@object.map do |item|
|
43
|
-
if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer)
|
44
|
-
serializer.new(item, @options)
|
45
|
-
else
|
46
|
-
item
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def as_json(*args)
|
52
|
-
@options[:hash] = hash = {}
|
53
|
-
@options[:unique_values] = {}
|
54
|
-
|
55
|
-
array = serializable_array.map do |item|
|
56
|
-
if item.is_a?(Hash)
|
57
|
-
item
|
58
|
-
elsif item.respond_to?(:serializable_hash)
|
59
|
-
item.serializable_hash
|
60
|
-
else
|
61
|
-
item.as_json
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
if root = @options[:root]
|
66
|
-
hash.merge!(root => array)
|
67
|
-
else
|
68
|
-
array
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
6
|
# Active Model Serializer
|
74
7
|
#
|
75
8
|
# Provides a basic serializer implementation that allows you to easily
|
76
9
|
# control how a given object is going to be serialized. On initialization,
|
77
|
-
# it expects
|
10
|
+
# it expects two objects as arguments, a resource and options. For example,
|
78
11
|
# one may do in a controller:
|
79
12
|
#
|
80
13
|
# PostSerializer.new(@post, :scope => current_user).to_json
|
@@ -83,7 +16,7 @@ module ActiveModel
|
|
83
16
|
# in for authorization purposes.
|
84
17
|
#
|
85
18
|
# We use the scope to check if a given attribute should be serialized or not.
|
86
|
-
# For example, some attributes
|
19
|
+
# For example, some attributes may only be returned if +current_user+ is the
|
87
20
|
# author of the post:
|
88
21
|
#
|
89
22
|
# class PostSerializer < ActiveModel::Serializer
|
@@ -99,11 +32,26 @@ module ActiveModel
|
|
99
32
|
# end
|
100
33
|
#
|
101
34
|
# def author?
|
102
|
-
# post.author ==
|
35
|
+
# post.author == scope
|
103
36
|
# end
|
104
37
|
# end
|
105
38
|
#
|
106
39
|
class Serializer
|
40
|
+
INCLUDE_METHODS = {}
|
41
|
+
INSTRUMENT = { :serialize => :"serialize.serializer", :associations => :"associations.serializer" }
|
42
|
+
|
43
|
+
class IncludeError < StandardError
|
44
|
+
attr_reader :source, :association
|
45
|
+
|
46
|
+
def initialize(source, association)
|
47
|
+
@source, @association = source, association
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"Cannot serialize #{association} when #{source} does not have a root!"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
107
55
|
module Associations #:nodoc:
|
108
56
|
class Config #:nodoc:
|
109
57
|
class_attribute :options
|
@@ -178,6 +126,10 @@ module ActiveModel
|
|
178
126
|
option(:include, source_serializer._root_embed)
|
179
127
|
end
|
180
128
|
|
129
|
+
def embeddable?
|
130
|
+
!associated_object.nil?
|
131
|
+
end
|
132
|
+
|
181
133
|
protected
|
182
134
|
|
183
135
|
def find_serializable(object)
|
@@ -212,13 +164,41 @@ module ActiveModel
|
|
212
164
|
end
|
213
165
|
|
214
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
|
+
|
215
183
|
def plural_key
|
216
|
-
|
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
|
217
189
|
end
|
218
190
|
|
219
191
|
def serialize
|
220
192
|
object = associated_object
|
221
|
-
|
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
|
222
202
|
end
|
223
203
|
|
224
204
|
def serialize_many
|
@@ -228,7 +208,14 @@ module ActiveModel
|
|
228
208
|
end
|
229
209
|
|
230
210
|
def serialize_ids
|
231
|
-
|
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
|
232
219
|
object.read_attribute_for_serialization(:id)
|
233
220
|
else
|
234
221
|
nil
|
@@ -259,11 +246,15 @@ module ActiveModel
|
|
259
246
|
end
|
260
247
|
|
261
248
|
def attribute(attr, options={})
|
262
|
-
self._attributes = _attributes.merge(attr => options[:key] || attr)
|
249
|
+
self._attributes = _attributes.merge(attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym)
|
263
250
|
|
264
251
|
unless method_defined?(attr)
|
265
|
-
|
252
|
+
define_method attr do
|
253
|
+
object.read_attribute_for_serialization(attr.to_sym)
|
254
|
+
end
|
266
255
|
end
|
256
|
+
|
257
|
+
define_include_method attr
|
267
258
|
end
|
268
259
|
|
269
260
|
def associate(klass, attrs) #:nodoc:
|
@@ -272,13 +263,29 @@ module ActiveModel
|
|
272
263
|
|
273
264
|
attrs.each do |attr|
|
274
265
|
unless method_defined?(attr)
|
275
|
-
|
266
|
+
define_method attr do
|
267
|
+
object.send attr
|
268
|
+
end
|
276
269
|
end
|
277
270
|
|
271
|
+
define_include_method attr
|
272
|
+
|
278
273
|
self._associations[attr] = klass.refine(attr, options)
|
279
274
|
end
|
280
275
|
end
|
281
276
|
|
277
|
+
def define_include_method(name)
|
278
|
+
method = "include_#{name}?".to_sym
|
279
|
+
|
280
|
+
INCLUDE_METHODS[name] = method
|
281
|
+
|
282
|
+
unless method_defined?(method)
|
283
|
+
define_method method do
|
284
|
+
true
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
282
289
|
# Defines an association in the object should be rendered.
|
283
290
|
#
|
284
291
|
# The serializer object should implement the association name
|
@@ -389,13 +396,12 @@ module ActiveModel
|
|
389
396
|
end
|
390
397
|
|
391
398
|
def url_options
|
392
|
-
@options[:url_options]
|
399
|
+
@options[:url_options] || {}
|
393
400
|
end
|
394
401
|
|
395
402
|
# Returns a json representation of the serializable
|
396
403
|
# object including the root.
|
397
|
-
def as_json(options=
|
398
|
-
options ||= {}
|
404
|
+
def as_json(options={})
|
399
405
|
if root = options.fetch(:root, @options.fetch(:root, _root))
|
400
406
|
@options[:hash] = hash = {}
|
401
407
|
@options[:unique_values] = {}
|
@@ -411,34 +417,22 @@ module ActiveModel
|
|
411
417
|
# object without the root.
|
412
418
|
def serializable_hash
|
413
419
|
instrument(:serialize, :serializer => self.class.name) do
|
414
|
-
node = attributes
|
420
|
+
@node = attributes
|
415
421
|
instrument :associations do
|
416
|
-
include_associations!
|
422
|
+
include_associations! if _embed
|
417
423
|
end
|
418
|
-
node
|
424
|
+
@node
|
419
425
|
end
|
420
426
|
end
|
421
427
|
|
422
|
-
def include_associations!
|
423
|
-
_associations.
|
424
|
-
|
425
|
-
|
426
|
-
if options.include?(:include) || options.include?(:exclude)
|
427
|
-
opts[:include] = included_association?(attr)
|
428
|
-
end
|
429
|
-
|
430
|
-
include! attr, opts
|
428
|
+
def include_associations!
|
429
|
+
_associations.each_key do |name|
|
430
|
+
include!(name) if include?(name)
|
431
431
|
end
|
432
432
|
end
|
433
433
|
|
434
|
-
def
|
435
|
-
|
436
|
-
options[:include].include?(name)
|
437
|
-
elsif options.key?(:exclude)
|
438
|
-
!options[:exclude].include?(name)
|
439
|
-
else
|
440
|
-
true
|
441
|
-
end
|
434
|
+
def include?(name)
|
435
|
+
send INCLUDE_METHODS[name]
|
442
436
|
end
|
443
437
|
|
444
438
|
def include!(name, options={})
|
@@ -460,9 +454,17 @@ module ActiveModel
|
|
460
454
|
@options[:unique_values] ||= {}
|
461
455
|
end
|
462
456
|
|
463
|
-
node = options[:node]
|
457
|
+
node = options[:node] ||= @node
|
464
458
|
value = options[:value]
|
465
459
|
|
460
|
+
if options[:include] == nil
|
461
|
+
if @options.key?(:include)
|
462
|
+
options[:include] = @options[:include].include?(name)
|
463
|
+
elsif @options.include?(:exclude)
|
464
|
+
options[:include] = !@options[:exclude].include?(name)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
466
468
|
association_class =
|
467
469
|
if klass = _associations[name]
|
468
470
|
klass
|
@@ -477,7 +479,9 @@ module ActiveModel
|
|
477
479
|
if association.embed_ids?
|
478
480
|
node[association.key] = association.serialize_ids
|
479
481
|
|
480
|
-
if association.embed_in_root?
|
482
|
+
if association.embed_in_root? && hash.nil?
|
483
|
+
raise IncludeError.new(self.class, association.name)
|
484
|
+
elsif association.embed_in_root? && association.embeddable?
|
481
485
|
merge_association hash, association.root, association.serialize_many, unique_values
|
482
486
|
end
|
483
487
|
elsif association.embed_objects?
|
@@ -510,25 +514,24 @@ module ActiveModel
|
|
510
514
|
hash = {}
|
511
515
|
|
512
516
|
_attributes.each do |name,key|
|
513
|
-
hash[key] = read_attribute_for_serialization(name)
|
517
|
+
hash[key] = read_attribute_for_serialization(name) if include?(name)
|
514
518
|
end
|
515
519
|
|
516
520
|
hash
|
517
521
|
end
|
518
522
|
|
523
|
+
# Returns options[:scope]
|
524
|
+
def scope
|
525
|
+
@options[:scope]
|
526
|
+
end
|
527
|
+
|
519
528
|
alias :read_attribute_for_serialization :send
|
520
529
|
|
521
530
|
# Use ActiveSupport::Notifications to send events to external systems.
|
522
531
|
# The event name is: name.class_name.serializer
|
523
532
|
def instrument(name, payload = {}, &block)
|
524
|
-
|
533
|
+
event_name = INSTRUMENT[name]
|
534
|
+
ActiveSupport::Notifications.instrument(event_name, payload, &block)
|
525
535
|
end
|
526
536
|
end
|
527
537
|
end
|
528
|
-
|
529
|
-
class Array
|
530
|
-
# Array uses ActiveModel::ArraySerializer.
|
531
|
-
def active_model_serializer
|
532
|
-
ActiveModel::ArraySerializer
|
533
|
-
end
|
534
|
-
end
|
@@ -2,7 +2,10 @@ require "active_support"
|
|
2
2
|
require "active_support/core_ext/string/inflections"
|
3
3
|
require "active_support/notifications"
|
4
4
|
require "active_model"
|
5
|
+
require "active_model/ordered_set"
|
6
|
+
require "active_model/array_serializer"
|
5
7
|
require "active_model/serializer"
|
8
|
+
require "set"
|
6
9
|
|
7
10
|
if defined?(Rails)
|
8
11
|
module ActiveModel
|
@@ -11,6 +14,7 @@ if defined?(Rails)
|
|
11
14
|
app ||= Rails.application # Rails 3.0.x does not yield `app`
|
12
15
|
|
13
16
|
Rails::Generators.configure!(app.config.generators)
|
17
|
+
Rails::Generators.hidden_namespaces.uniq!
|
14
18
|
require "generators/resource_override"
|
15
19
|
end
|
16
20
|
|
@@ -56,6 +60,19 @@ ActiveSupport.on_load(:active_record) do
|
|
56
60
|
include ActiveModel::SerializerSupport
|
57
61
|
end
|
58
62
|
|
63
|
+
module ActiveModel::ArraySerializerSupport
|
64
|
+
def active_model_serializer
|
65
|
+
ActiveModel::ArraySerializer
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
Array.send(:include, ActiveModel::ArraySerializerSupport)
|
70
|
+
Set.send(:include, ActiveModel::ArraySerializerSupport)
|
71
|
+
|
72
|
+
ActiveSupport.on_load(:active_record) do
|
73
|
+
ActiveRecord::Relation.send(:include, ActiveModel::ArraySerializerSupport)
|
74
|
+
end
|
75
|
+
|
59
76
|
begin
|
60
77
|
require 'action_controller'
|
61
78
|
require 'action_controller/serialization'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class NoSerializationScopeTest < ActionController::TestCase
|
4
|
+
class ScopeSerializer
|
5
|
+
def initialize(object, options)
|
6
|
+
@object, @options = object, options
|
7
|
+
end
|
8
|
+
|
9
|
+
def as_json(*)
|
10
|
+
{ :scope => @options[:scope].as_json }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ScopeSerializable
|
15
|
+
def active_model_serializer
|
16
|
+
ScopeSerializer
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class NoSerializationScopeController < ActionController::Base
|
21
|
+
serialization_scope nil
|
22
|
+
|
23
|
+
def index
|
24
|
+
render :json => ScopeSerializable.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
tests NoSerializationScopeController
|
29
|
+
|
30
|
+
def test_disabled_serialization_scope
|
31
|
+
get :index
|
32
|
+
assert_equal '{"scope":null}', @response.body
|
33
|
+
end
|
34
|
+
end
|