active_model_serializers 0.10.0.rc5 → 0.10.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.
- checksums.yaml +4 -4
- data/.gitignore +10 -0
- data/.travis.yml +0 -8
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +14 -4
- data/Gemfile +1 -2
- data/README.md +3 -3
- data/active_model_serializers.gemspec +1 -1
- data/appveyor.yml +6 -10
- data/docs/ARCHITECTURE.md +1 -1
- data/docs/README.md +1 -0
- data/docs/general/deserialization.md +19 -19
- data/docs/general/serializers.md +33 -0
- data/docs/howto/serialize_poro.md +32 -0
- data/lib/active_model/serializer.rb +2 -0
- data/lib/active_model/serializer/caching.rb +185 -3
- data/lib/active_model/serializer/field.rb +36 -2
- data/lib/active_model/serializer/lint.rb +8 -18
- data/lib/active_model/serializer/version.rb +1 -1
- data/lib/active_model_serializers.rb +0 -2
- data/lib/active_model_serializers/adapter/attributes.rb +20 -38
- data/lib/active_model_serializers/adapter/base.rb +8 -15
- data/lib/active_model_serializers/adapter/json.rb +12 -2
- data/lib/active_model_serializers/adapter/json_api.rb +33 -30
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +10 -5
- data/lib/active_model_serializers/adapter/null.rb +1 -2
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +3 -2
- data/lib/active_model_serializers/serializable_resource.rb +1 -1
- data/lib/active_model_serializers/test/schema.rb +44 -9
- data/test/action_controller/json_api/transform_test.rb +2 -1
- data/test/action_controller/serialization_test.rb +3 -1
- data/test/active_model_serializers/test/schema_test.rb +5 -3
- data/test/active_model_serializers/test/serializer_test.rb +1 -2
- data/test/adapter/json/transform_test.rb +14 -14
- data/test/adapter/json_api/has_many_test.rb +3 -2
- data/test/adapter/json_api/has_one_test.rb +3 -2
- data/test/adapter/json_api/pagination_links_test.rb +39 -21
- data/test/adapter/json_api/transform_test.rb +36 -34
- data/test/adapter/polymorphic_test.rb +111 -12
- data/test/adapter_test.rb +27 -0
- data/test/array_serializer_test.rb +10 -25
- data/test/benchmark/bm_caching.rb +17 -15
- data/test/benchmark/controllers.rb +9 -2
- data/test/benchmark/fixtures.rb +56 -4
- data/test/cache_test.rb +103 -6
- data/test/fixtures/active_record.rb +10 -0
- data/test/fixtures/poro.rb +31 -3
- data/test/serializers/associations_test.rb +43 -15
- data/test/serializers/attribute_test.rb +44 -16
- data/test/serializers/meta_test.rb +5 -7
- data/test/support/isolated_unit.rb +0 -1
- data/test/test_helper.rb +19 -21
- metadata +7 -12
- data/lib/active_model_serializers/cached_serializer.rb +0 -87
- data/lib/active_model_serializers/fragment_cache.rb +0 -118
- data/test/active_model_serializers/cached_serializer_test.rb +0 -80
- data/test/active_model_serializers/fragment_cache_test.rb +0 -34
@@ -4,6 +4,12 @@ module ActiveModel
|
|
4
4
|
# specified in the ActiveModel::Serializer class.
|
5
5
|
# Notice that the field block is evaluated in the context of the serializer.
|
6
6
|
Field = Struct.new(:name, :options, :block) do
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
|
10
|
+
validate_condition!
|
11
|
+
end
|
12
|
+
|
7
13
|
# Compute the actual value of a field for a given serializer instance.
|
8
14
|
# @param [Serializer] The serializer instance for which the value is computed.
|
9
15
|
# @return [Object] value
|
@@ -27,9 +33,9 @@ module ActiveModel
|
|
27
33
|
def excluded?(serializer)
|
28
34
|
case condition_type
|
29
35
|
when :if
|
30
|
-
!serializer
|
36
|
+
!evaluate_condition(serializer)
|
31
37
|
when :unless
|
32
|
-
serializer
|
38
|
+
evaluate_condition(serializer)
|
33
39
|
else
|
34
40
|
false
|
35
41
|
end
|
@@ -37,6 +43,34 @@ module ActiveModel
|
|
37
43
|
|
38
44
|
private
|
39
45
|
|
46
|
+
def validate_condition!
|
47
|
+
return if condition_type == :none
|
48
|
+
|
49
|
+
case condition
|
50
|
+
when Symbol, String, Proc
|
51
|
+
# noop
|
52
|
+
else
|
53
|
+
fail TypeError, "#{condition_type.inspect} should be a Symbol, String or Proc"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def evaluate_condition(serializer)
|
58
|
+
case condition
|
59
|
+
when Symbol
|
60
|
+
serializer.public_send(condition)
|
61
|
+
when String
|
62
|
+
serializer.instance_eval(condition)
|
63
|
+
when Proc
|
64
|
+
if condition.arity.zero?
|
65
|
+
serializer.instance_exec(&condition)
|
66
|
+
else
|
67
|
+
serializer.instance_exec(serializer, &condition)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
40
74
|
def condition_type
|
41
75
|
@condition_type ||=
|
42
76
|
if options.key?(:if)
|
@@ -36,16 +36,10 @@ module ActiveModel::Serializer::Lint
|
|
36
36
|
def test_read_attribute_for_serialization
|
37
37
|
assert_respond_to resource, :read_attribute_for_serialization, 'The resource should respond to read_attribute_for_serialization'
|
38
38
|
actual_arity = resource.method(:read_attribute_for_serialization).arity
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
else
|
44
|
-
# using absolute value since arity is:
|
45
|
-
# 1 for def read_attribute_for_serialization(name); end
|
46
|
-
# -1 for alias :read_attribute_for_serialization :send
|
47
|
-
assert_includes [1, -1], actual_arity, "expected #{actual_arity.inspect} to be 1 or -1"
|
48
|
-
end
|
39
|
+
# using absolute value since arity is:
|
40
|
+
# 1 for def read_attribute_for_serialization(name); end
|
41
|
+
# -1 for alias :read_attribute_for_serialization :send
|
42
|
+
assert_equal 1, actual_arity.abs, "expected #{actual_arity.inspect}.abs to be 1 or -1"
|
49
43
|
end
|
50
44
|
|
51
45
|
# Passes if the object responds to <tt>as_json</tt> and if it takes
|
@@ -76,19 +70,15 @@ module ActiveModel::Serializer::Lint
|
|
76
70
|
resource.to_json(nil)
|
77
71
|
end
|
78
72
|
|
79
|
-
# Passes if the object responds to <tt>cache_key</tt>
|
80
|
-
# arguments (Rails 4.0) or a splat (Rails 4.1+).
|
73
|
+
# Passes if the object responds to <tt>cache_key</tt>
|
81
74
|
# Fails otherwise.
|
82
75
|
#
|
83
|
-
# <tt>cache_key</tt> returns a (self-expiring) unique key for the object,
|
84
|
-
# is part of the (self-expiring) cache_key, which is used by the
|
85
|
-
# It is not required unless caching is enabled.
|
76
|
+
# <tt>cache_key</tt> returns a (self-expiring) unique key for the object,
|
77
|
+
# and is part of the (self-expiring) cache_key, which is used by the
|
78
|
+
# adapter. It is not required unless caching is enabled.
|
86
79
|
def test_cache_key
|
87
80
|
assert_respond_to resource, :cache_key
|
88
81
|
actual_arity = resource.method(:cache_key).arity
|
89
|
-
# using absolute value since arity is:
|
90
|
-
# 0 for Rails 4.1+, *timestamp_names
|
91
|
-
# -1 for Rails 4.0, no arguments
|
92
82
|
assert_includes [-1, 0], actual_arity, "expected #{actual_arity.inspect} to be 0 or -1"
|
93
83
|
end
|
94
84
|
|
@@ -8,7 +8,7 @@ module ActiveModelSerializers
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def serializable_hash(options = nil)
|
11
|
-
options
|
11
|
+
options = serialization_options(options)
|
12
12
|
|
13
13
|
if serializer.respond_to?(:each)
|
14
14
|
serializable_hash_for_collection(options)
|
@@ -25,33 +25,6 @@ module ActiveModelSerializers
|
|
25
25
|
serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
|
26
26
|
end
|
27
27
|
|
28
|
-
# Read cache from cache_store
|
29
|
-
# @return [Hash]
|
30
|
-
def cache_read_multi
|
31
|
-
return {} if ActiveModelSerializers.config.cache_store.blank?
|
32
|
-
|
33
|
-
keys = CachedSerializer.object_cache_keys(serializer, self, @include_tree)
|
34
|
-
|
35
|
-
return {} if keys.blank?
|
36
|
-
|
37
|
-
ActiveModelSerializers.config.cache_store.read_multi(*keys)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Set @cached_attributes
|
41
|
-
def cache_attributes
|
42
|
-
return if @cached_attributes.present?
|
43
|
-
|
44
|
-
@cached_attributes = cache_read_multi
|
45
|
-
end
|
46
|
-
|
47
|
-
# Get attributes from @cached_attributes
|
48
|
-
# @return [Hash] cached attributes
|
49
|
-
def cached_attributes(cached_serializer)
|
50
|
-
return yield unless cached_serializer.cached?
|
51
|
-
|
52
|
-
@cached_attributes.fetch(cached_serializer.cache_key(self)) { yield }
|
53
|
-
end
|
54
|
-
|
55
28
|
def serializable_hash_for_single_resource(options)
|
56
29
|
resource = resource_object_for(options)
|
57
30
|
relationships = resource_relationships(options)
|
@@ -61,7 +34,7 @@ module ActiveModelSerializers
|
|
61
34
|
def resource_relationships(options)
|
62
35
|
relationships = {}
|
63
36
|
serializer.associations(@include_tree).each do |association|
|
64
|
-
relationships[association.key]
|
37
|
+
relationships[association.key] ||= relationship_value_for(association, options)
|
65
38
|
end
|
66
39
|
|
67
40
|
relationships
|
@@ -72,21 +45,30 @@ module ActiveModelSerializers
|
|
72
45
|
return unless association.serializer && association.serializer.object
|
73
46
|
|
74
47
|
opts = instance_options.merge(include: @include_tree[association.key])
|
75
|
-
Attributes.new(association.serializer, opts).serializable_hash(options)
|
48
|
+
relationship_value = Attributes.new(association.serializer, opts).serializable_hash(options)
|
49
|
+
|
50
|
+
if association.options[:polymorphic] && relationship_value
|
51
|
+
polymorphic_type = association.serializer.object.class.name.underscore
|
52
|
+
relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
|
53
|
+
end
|
54
|
+
|
55
|
+
relationship_value
|
76
56
|
end
|
77
57
|
|
78
|
-
#
|
79
|
-
def
|
80
|
-
|
58
|
+
# Set @cached_attributes
|
59
|
+
def cache_attributes
|
60
|
+
return if @cached_attributes.present?
|
61
|
+
|
62
|
+
@cached_attributes = ActiveModel::Serializer.cache_read_multi(serializer, self, @include_tree)
|
81
63
|
end
|
82
64
|
|
83
65
|
def resource_object_for(options)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
cached_serializer.cache_check(self) do
|
88
|
-
serializer.attributes(options[:fields])
|
66
|
+
if serializer.class.cache_enabled?
|
67
|
+
@cached_attributes.fetch(serializer.cache_key(self)) do
|
68
|
+
serializer.cached_fields(options[:fields], self)
|
89
69
|
end
|
70
|
+
else
|
71
|
+
serializer.cached_fields(options[:fields], self)
|
90
72
|
end
|
91
73
|
end
|
92
74
|
end
|
@@ -19,14 +19,14 @@ module ActiveModelSerializers
|
|
19
19
|
@cached_name ||= self.class.name.demodulize.underscore
|
20
20
|
end
|
21
21
|
|
22
|
+
# Subclasses that implement this method must first call
|
23
|
+
# options = serialization_options(options)
|
22
24
|
def serializable_hash(_options = nil)
|
23
25
|
fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
24
26
|
end
|
25
27
|
|
26
28
|
def as_json(options = nil)
|
27
|
-
|
28
|
-
include_meta(hash)
|
29
|
-
hash
|
29
|
+
serializable_hash(options)
|
30
30
|
end
|
31
31
|
|
32
32
|
def fragment_cache(cached_hash, non_cached_hash)
|
@@ -34,30 +34,23 @@ module ActiveModelSerializers
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def cache_check(serializer)
|
37
|
-
|
37
|
+
serializer.cache_check(self) do
|
38
38
|
yield
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
def meta_key
|
49
|
-
instance_options.fetch(:meta_key, 'meta'.freeze)
|
44
|
+
# see https://github.com/rails-api/active_model_serializers/pull/965
|
45
|
+
# When <tt>options</tt> is +nil+, sets it to +{}+
|
46
|
+
def serialization_options(options)
|
47
|
+
options ||= {} # rubocop:disable Lint/UselessAssignment
|
50
48
|
end
|
51
49
|
|
52
50
|
def root
|
53
51
|
serializer.json_key.to_sym if serializer.json_key
|
54
52
|
end
|
55
53
|
|
56
|
-
def include_meta(json)
|
57
|
-
json[meta_key] = meta unless meta.blank?
|
58
|
-
json
|
59
|
-
end
|
60
|
-
|
61
54
|
class << self
|
62
55
|
# Sets the default transform for the adapter.
|
63
56
|
#
|
@@ -2,9 +2,19 @@ module ActiveModelSerializers
|
|
2
2
|
module Adapter
|
3
3
|
class Json < Base
|
4
4
|
def serializable_hash(options = nil)
|
5
|
-
options
|
5
|
+
options = serialization_options(options)
|
6
6
|
serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) }
|
7
|
-
|
7
|
+
serialized_hash[meta_key] = meta unless meta.blank?
|
8
|
+
|
9
|
+
self.class.transform_key_casing!(serialized_hash, instance_options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def meta
|
13
|
+
instance_options.fetch(:meta, nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def meta_key
|
17
|
+
instance_options.fetch(:meta_key, 'meta'.freeze)
|
8
18
|
end
|
9
19
|
end
|
10
20
|
end
|
@@ -43,14 +43,13 @@ module ActiveModelSerializers
|
|
43
43
|
|
44
44
|
# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
|
45
45
|
# {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
|
46
|
-
def serializable_hash(
|
47
|
-
options ||= {}
|
46
|
+
def serializable_hash(*)
|
48
47
|
document = if serializer.success?
|
49
|
-
success_document
|
48
|
+
success_document
|
50
49
|
else
|
51
|
-
failure_document
|
50
|
+
failure_document
|
52
51
|
end
|
53
|
-
self.class.transform_key_casing!(document,
|
52
|
+
self.class.transform_key_casing!(document, instance_options)
|
54
53
|
end
|
55
54
|
|
56
55
|
# {http://jsonapi.org/format/#document-top-level Primary data}
|
@@ -68,10 +67,11 @@ module ActiveModelSerializers
|
|
68
67
|
# links: toplevel_links,
|
69
68
|
# jsonapi: toplevel_jsonapi
|
70
69
|
# }.reject! {|_,v| v.nil? }
|
71
|
-
|
70
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
71
|
+
def success_document
|
72
72
|
is_collection = serializer.respond_to?(:each)
|
73
73
|
serializers = is_collection ? serializer : [serializer]
|
74
|
-
primary_data, included = resource_objects_for(serializers
|
74
|
+
primary_data, included = resource_objects_for(serializers)
|
75
75
|
|
76
76
|
hash = {}
|
77
77
|
# toplevel_data
|
@@ -128,11 +128,14 @@ module ActiveModelSerializers
|
|
128
128
|
|
129
129
|
if is_collection && serializer.paginated?
|
130
130
|
hash[:links] ||= {}
|
131
|
-
hash[:links].update(pagination_links_for(serializer
|
131
|
+
hash[:links].update(pagination_links_for(serializer))
|
132
132
|
end
|
133
133
|
|
134
|
+
hash[:meta] = instance_options[:meta] unless instance_options[:meta].blank?
|
135
|
+
|
134
136
|
hash
|
135
137
|
end
|
138
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
136
139
|
|
137
140
|
# {http://jsonapi.org/format/#errors JSON API Errors}
|
138
141
|
# TODO: look into caching
|
@@ -148,7 +151,7 @@ module ActiveModelSerializers
|
|
148
151
|
# }.reject! {|_,v| v.nil? }
|
149
152
|
# prs:
|
150
153
|
# https://github.com/rails-api/active_model_serializers/pull/1004
|
151
|
-
def failure_document
|
154
|
+
def failure_document
|
152
155
|
hash = {}
|
153
156
|
# PR Please :)
|
154
157
|
# Jsonapi.add!(hash)
|
@@ -163,10 +166,10 @@ module ActiveModelSerializers
|
|
163
166
|
# ]
|
164
167
|
if serializer.respond_to?(:each)
|
165
168
|
hash[:errors] = serializer.flat_map do |error_serializer|
|
166
|
-
Error.resource_errors(error_serializer,
|
169
|
+
Error.resource_errors(error_serializer, instance_options)
|
167
170
|
end
|
168
171
|
else
|
169
|
-
hash[:errors] = Error.resource_errors(serializer,
|
172
|
+
hash[:errors] = Error.resource_errors(serializer, instance_options)
|
170
173
|
end
|
171
174
|
hash
|
172
175
|
end
|
@@ -224,21 +227,21 @@ module ActiveModelSerializers
|
|
224
227
|
# [x] url helpers https://github.com/rails-api/active_model_serializers/issues/1269
|
225
228
|
# meta
|
226
229
|
# [x] https://github.com/rails-api/active_model_serializers/pull/1340
|
227
|
-
def resource_objects_for(serializers
|
230
|
+
def resource_objects_for(serializers)
|
228
231
|
@primary = []
|
229
232
|
@included = []
|
230
233
|
@resource_identifiers = Set.new
|
231
|
-
serializers.each { |serializer| process_resource(serializer, true
|
232
|
-
serializers.each { |serializer| process_relationships(serializer, @include_tree
|
234
|
+
serializers.each { |serializer| process_resource(serializer, true) }
|
235
|
+
serializers.each { |serializer| process_relationships(serializer, @include_tree) }
|
233
236
|
|
234
237
|
[@primary, @included]
|
235
238
|
end
|
236
239
|
|
237
|
-
def process_resource(serializer, primary
|
238
|
-
resource_identifier = ResourceIdentifier.new(serializer,
|
240
|
+
def process_resource(serializer, primary)
|
241
|
+
resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
|
239
242
|
return false unless @resource_identifiers.add?(resource_identifier)
|
240
243
|
|
241
|
-
resource_object = resource_object_for(serializer
|
244
|
+
resource_object = resource_object_for(serializer)
|
242
245
|
if primary
|
243
246
|
@primary << resource_object
|
244
247
|
else
|
@@ -248,21 +251,21 @@ module ActiveModelSerializers
|
|
248
251
|
true
|
249
252
|
end
|
250
253
|
|
251
|
-
def process_relationships(serializer, include_tree
|
254
|
+
def process_relationships(serializer, include_tree)
|
252
255
|
serializer.associations(include_tree).each do |association|
|
253
|
-
process_relationship(association.serializer, include_tree[association.key]
|
256
|
+
process_relationship(association.serializer, include_tree[association.key])
|
254
257
|
end
|
255
258
|
end
|
256
259
|
|
257
|
-
def process_relationship(serializer, include_tree
|
260
|
+
def process_relationship(serializer, include_tree)
|
258
261
|
if serializer.respond_to?(:each)
|
259
|
-
serializer.each { |s| process_relationship(s, include_tree
|
262
|
+
serializer.each { |s| process_relationship(s, include_tree) }
|
260
263
|
return
|
261
264
|
end
|
262
265
|
return unless serializer && serializer.object
|
263
|
-
return unless process_resource(serializer, false
|
266
|
+
return unless process_resource(serializer, false)
|
264
267
|
|
265
|
-
process_relationships(serializer, include_tree
|
268
|
+
process_relationships(serializer, include_tree)
|
266
269
|
end
|
267
270
|
|
268
271
|
# {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
|
@@ -286,9 +289,9 @@ module ActiveModelSerializers
|
|
286
289
|
end
|
287
290
|
|
288
291
|
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
|
289
|
-
def resource_object_for(serializer
|
292
|
+
def resource_object_for(serializer)
|
290
293
|
resource_object = cache_check(serializer) do
|
291
|
-
resource_object = ResourceIdentifier.new(serializer,
|
294
|
+
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
292
295
|
|
293
296
|
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
294
297
|
attributes = attributes_for(serializer, requested_fields)
|
@@ -297,7 +300,7 @@ module ActiveModelSerializers
|
|
297
300
|
end
|
298
301
|
|
299
302
|
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
300
|
-
relationships = relationships_for(serializer, requested_associations
|
303
|
+
relationships = relationships_for(serializer, requested_associations)
|
301
304
|
resource_object[:relationships] = relationships if relationships.any?
|
302
305
|
|
303
306
|
links = links_for(serializer)
|
@@ -425,13 +428,13 @@ module ActiveModelSerializers
|
|
425
428
|
# id: 'required-id',
|
426
429
|
# meta: meta
|
427
430
|
# }.reject! {|_,v| v.nil? }
|
428
|
-
def relationships_for(serializer, requested_associations
|
431
|
+
def relationships_for(serializer, requested_associations)
|
429
432
|
include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations)
|
430
433
|
serializer.associations(include_tree).each_with_object({}) do |association, hash|
|
431
434
|
hash[association.key] = Relationship.new(
|
432
435
|
serializer,
|
433
436
|
association.serializer,
|
434
|
-
|
437
|
+
instance_options,
|
435
438
|
options: association.options,
|
436
439
|
links: association.links,
|
437
440
|
meta: association.meta
|
@@ -499,8 +502,8 @@ module ActiveModelSerializers
|
|
499
502
|
# end
|
500
503
|
# prs:
|
501
504
|
# https://github.com/rails-api/active_model_serializers/pull/1041
|
502
|
-
def pagination_links_for(serializer
|
503
|
-
PaginationLinks.new(serializer.object,
|
505
|
+
def pagination_links_for(serializer)
|
506
|
+
PaginationLinks.new(serializer.object, instance_options).as_json
|
504
507
|
end
|
505
508
|
|
506
509
|
# {http://jsonapi.org/format/#document-meta Docment Meta}
|