active_model_serializers 0.10.0.rc5 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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}
|