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
@@ -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
|