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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.travis.yml +0 -8
  4. data/CHANGELOG.md +23 -0
  5. data/CONTRIBUTING.md +14 -4
  6. data/Gemfile +1 -2
  7. data/README.md +3 -3
  8. data/active_model_serializers.gemspec +1 -1
  9. data/appveyor.yml +6 -10
  10. data/docs/ARCHITECTURE.md +1 -1
  11. data/docs/README.md +1 -0
  12. data/docs/general/deserialization.md +19 -19
  13. data/docs/general/serializers.md +33 -0
  14. data/docs/howto/serialize_poro.md +32 -0
  15. data/lib/active_model/serializer.rb +2 -0
  16. data/lib/active_model/serializer/caching.rb +185 -3
  17. data/lib/active_model/serializer/field.rb +36 -2
  18. data/lib/active_model/serializer/lint.rb +8 -18
  19. data/lib/active_model/serializer/version.rb +1 -1
  20. data/lib/active_model_serializers.rb +0 -2
  21. data/lib/active_model_serializers/adapter/attributes.rb +20 -38
  22. data/lib/active_model_serializers/adapter/base.rb +8 -15
  23. data/lib/active_model_serializers/adapter/json.rb +12 -2
  24. data/lib/active_model_serializers/adapter/json_api.rb +33 -30
  25. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +10 -5
  26. data/lib/active_model_serializers/adapter/null.rb +1 -2
  27. data/lib/active_model_serializers/register_jsonapi_renderer.rb +3 -2
  28. data/lib/active_model_serializers/serializable_resource.rb +1 -1
  29. data/lib/active_model_serializers/test/schema.rb +44 -9
  30. data/test/action_controller/json_api/transform_test.rb +2 -1
  31. data/test/action_controller/serialization_test.rb +3 -1
  32. data/test/active_model_serializers/test/schema_test.rb +5 -3
  33. data/test/active_model_serializers/test/serializer_test.rb +1 -2
  34. data/test/adapter/json/transform_test.rb +14 -14
  35. data/test/adapter/json_api/has_many_test.rb +3 -2
  36. data/test/adapter/json_api/has_one_test.rb +3 -2
  37. data/test/adapter/json_api/pagination_links_test.rb +39 -21
  38. data/test/adapter/json_api/transform_test.rb +36 -34
  39. data/test/adapter/polymorphic_test.rb +111 -12
  40. data/test/adapter_test.rb +27 -0
  41. data/test/array_serializer_test.rb +10 -25
  42. data/test/benchmark/bm_caching.rb +17 -15
  43. data/test/benchmark/controllers.rb +9 -2
  44. data/test/benchmark/fixtures.rb +56 -4
  45. data/test/cache_test.rb +103 -6
  46. data/test/fixtures/active_record.rb +10 -0
  47. data/test/fixtures/poro.rb +31 -3
  48. data/test/serializers/associations_test.rb +43 -15
  49. data/test/serializers/attribute_test.rb +44 -16
  50. data/test/serializers/meta_test.rb +5 -7
  51. data/test/support/isolated_unit.rb +0 -1
  52. data/test/test_helper.rb +19 -21
  53. metadata +7 -12
  54. data/lib/active_model_serializers/cached_serializer.rb +0 -87
  55. data/lib/active_model_serializers/fragment_cache.rb +0 -118
  56. data/test/active_model_serializers/cached_serializer_test.rb +0 -80
  57. 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.public_send(condition)
36
+ !evaluate_condition(serializer)
31
37
  when :unless
32
- serializer.public_send(condition)
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
- if defined?(::Rubinius)
40
- # 1 for def read_attribute_for_serialization(name); end
41
- # -2 for alias :read_attribute_for_serialization :send for rbx because :shrug:
42
- assert_includes [1, -2], actual_arity, "expected #{actual_arity.inspect} to be 1 or -2"
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> and if it takes no
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, and
84
- # is part of the (self-expiring) cache_key, which is used by the adapter.
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
 
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
- VERSION = '0.10.0.rc5'.freeze
3
+ VERSION = '0.10.0'.freeze
4
4
  end
5
5
  end
@@ -6,8 +6,6 @@ require 'active_support/json'
6
6
  module ActiveModelSerializers
7
7
  extend ActiveSupport::Autoload
8
8
  autoload :Model
9
- autoload :CachedSerializer
10
- autoload :FragmentCache
11
9
  autoload :Callbacks
12
10
  autoload :Deserialization
13
11
  autoload :SerializableResource
@@ -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] = relationship_value_for(association, options)
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
- # no-op: Attributes adapter does not include meta data, because it does not support root.
79
- def include_meta(json)
80
- json
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
- cached_serializer = CachedSerializer.new(serializer)
85
-
86
- cached_attributes(cached_serializer) do
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
- hash = serializable_hash(options)
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
- CachedSerializer.new(serializer).cache_check(self) do
37
+ serializer.cache_check(self) do
38
38
  yield
39
39
  end
40
40
  end
41
41
 
42
42
  private
43
43
 
44
- def meta
45
- instance_options.fetch(:meta, nil)
46
- end
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
- self.class.transform_key_casing!(serialized_hash, options)
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(options = nil)
47
- options ||= {}
46
+ def serializable_hash(*)
48
47
  document = if serializer.success?
49
- success_document(options)
48
+ success_document
50
49
  else
51
- failure_document(options)
50
+ failure_document
52
51
  end
53
- self.class.transform_key_casing!(document, options)
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
- def success_document(options)
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, options)
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, options))
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(options)
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, options)
169
+ Error.resource_errors(error_serializer, instance_options)
167
170
  end
168
171
  else
169
- hash[:errors] = Error.resource_errors(serializer, options)
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, options)
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, options) }
232
- serializers.each { |serializer| process_relationships(serializer, @include_tree, options) }
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, options)
238
- resource_identifier = ResourceIdentifier.new(serializer, options).as_json
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, options)
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, options)
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], options)
256
+ process_relationship(association.serializer, include_tree[association.key])
254
257
  end
255
258
  end
256
259
 
257
- def process_relationship(serializer, include_tree, options)
260
+ def process_relationship(serializer, include_tree)
258
261
  if serializer.respond_to?(:each)
259
- serializer.each { |s| process_relationship(s, include_tree, options) }
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, options)
266
+ return unless process_resource(serializer, false)
264
267
 
265
- process_relationships(serializer, include_tree, options)
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, options)
292
+ def resource_object_for(serializer)
290
293
  resource_object = cache_check(serializer) do
291
- resource_object = ResourceIdentifier.new(serializer, options).as_json
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, options)
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, options)
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
- options,
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, options)
503
- PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options)
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}