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.
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}