serega 0.37.2 → 0.39.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +124 -26
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +3 -3
  5. data/lib/serega/attribute_normalizer.rb +87 -14
  6. data/lib/serega/attribute_value_resolvers/batch.rb +4 -0
  7. data/lib/serega/config.rb +48 -1
  8. data/lib/serega/engine/level.rb +57 -0
  9. data/lib/serega/engine/level_queue.rb +54 -0
  10. data/lib/serega/{batch → engine}/loader.rb +1 -1
  11. data/lib/serega/object_serializer.rb +61 -56
  12. data/lib/serega/plan.rb +0 -9
  13. data/lib/serega/plan_point.rb +56 -5
  14. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +21 -1
  15. data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +3 -2
  16. data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +6 -4
  17. data/lib/serega/plugins/if/if.rb +9 -10
  18. data/lib/serega/plugins/presenter/presenter.rb +101 -5
  19. data/lib/serega/plugins/root/root.rb +1 -1
  20. data/lib/serega/utils/collection_detector.rb +26 -0
  21. data/lib/serega/validations/attribute/check_block.rb +20 -55
  22. data/lib/serega/validations/attribute/check_opt_base_serializer.rb +37 -0
  23. data/lib/serega/validations/attribute/check_opt_batch.rb +6 -6
  24. data/lib/serega/validations/attribute/check_opt_const.rb +3 -4
  25. data/lib/serega/validations/attribute/check_opt_delegate.rb +3 -4
  26. data/lib/serega/validations/attribute/check_opt_many.rb +8 -7
  27. data/lib/serega/validations/attribute/check_opt_method.rb +3 -4
  28. data/lib/serega/validations/attribute/check_opt_serializer.rb +4 -1
  29. data/lib/serega/validations/attribute/check_opt_value.rb +3 -4
  30. data/lib/serega/validations/check_attribute_params.rb +8 -7
  31. data/lib/serega.rb +33 -29
  32. metadata +6 -5
  33. data/lib/serega/batch/attribute_loader.rb +0 -82
  34. data/lib/serega/batch/attribute_loaders.rb +0 -117
  35. data/lib/serega/batch/level.rb +0 -48
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaValidations
5
+ module Attribute
6
+ #
7
+ # Attribute `:base_serializer` option validator
8
+ #
9
+ class CheckOptBaseSerializer
10
+ class << self
11
+ #
12
+ # Checks attribute :base_serializer option. It specifies the parent
13
+ # class for the nested serializer defined with the attribute block,
14
+ # so it makes sense only when a block is provided.
15
+ #
16
+ # @param opts [Hash] Attribute options
17
+ # @param block [nil, Proc] Attribute block (defines a nested serializer)
18
+ #
19
+ # @raise [SeregaError] SeregaError that option has invalid value
20
+ #
21
+ # @return [void]
22
+ #
23
+ def call(opts, block = nil)
24
+ return unless opts.key?(:base_serializer)
25
+
26
+ raise SeregaError, "Option :base_serializer can be used only with a block" unless block
27
+
28
+ value = opts[:base_serializer]
29
+ return if value.is_a?(Class) && (value <= Serega)
30
+
31
+ raise SeregaError, "Invalid option :base_serializer => #{value.inspect}. Must be a Serega subclass"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -17,11 +17,11 @@ class Serega
17
17
  #
18
18
  # @return [void]
19
19
  #
20
- def call(serializer_class, opts, block)
20
+ def call(serializer_class, opts)
21
21
  return unless opts.key?(:batch)
22
22
 
23
23
  check_opt_batch(opts, serializer_class)
24
- check_usage_with_other_params(opts, block)
24
+ check_usage_with_other_params(opts)
25
25
  end
26
26
 
27
27
  private
@@ -72,22 +72,22 @@ class Serega
72
72
  Utils::CheckAllowedKeys.call(batch_opts, %i[use id], :batch)
73
73
  end
74
74
 
75
- def check_usage_with_other_params(opts, block)
75
+ def check_usage_with_other_params(opts)
76
76
  batch = opts[:batch]
77
77
  use_id = batch.is_a?(Hash) && batch.key?(:id)
78
78
  use_multiple = batch.is_a?(Hash) && (Array(batch[:use]).size > 1)
79
- value_added = opts.key?(:value) || block
79
+ value_added = opts.key?(:value)
80
80
 
81
81
  if use_multiple && use_id
82
82
  raise SeregaError, "Option `batch.id` should not be used with multiple loaders provided in `batch.use`"
83
83
  end
84
84
 
85
85
  if use_multiple && !value_added
86
- raise SeregaError, "Attribute :value option or block should be provided when selecting multiple batch loaders"
86
+ raise SeregaError, "Attribute :value option should be provided when selecting multiple batch loaders"
87
87
  end
88
88
 
89
89
  if use_id && value_added
90
- raise SeregaError, "Option `batch.id` should not be used when :value or block provided directly"
90
+ raise SeregaError, "Option `batch.id` should not be used when :value option provided directly"
91
91
  end
92
92
 
93
93
  raise SeregaError, "Option :batch can not be used together with option :method" if opts.key?(:method)
@@ -17,19 +17,18 @@ class Serega
17
17
  #
18
18
  # @return [void]
19
19
  #
20
- def call(opts, block = nil)
20
+ def call(opts)
21
21
  return unless opts.key?(:const)
22
22
 
23
- check_usage_with_other_params(opts, block)
23
+ check_usage_with_other_params(opts)
24
24
  end
25
25
 
26
26
  private
27
27
 
28
- def check_usage_with_other_params(opts, block)
28
+ def check_usage_with_other_params(opts)
29
29
  raise SeregaError, "Option :const can not be used together with option :method" if opts.key?(:method)
30
30
  raise SeregaError, "Option :const can not be used together with option :value" if opts.key?(:value)
31
31
  raise SeregaError, "Option :const can not be used together with option :batch" if opts.key?(:batch)
32
- raise SeregaError, "Option :const can not be used together with block" if block
33
32
  end
34
33
  end
35
34
  end
@@ -18,11 +18,11 @@ class Serega
18
18
  #
19
19
  # @return [void]
20
20
  #
21
- def call(opts, block = nil)
21
+ def call(opts)
22
22
  return unless opts.key?(:delegate)
23
23
 
24
24
  check_opt_delegate(opts)
25
- check_usage_with_other_params(opts, block)
25
+ check_usage_with_other_params(opts)
26
26
  end
27
27
 
28
28
  private
@@ -56,12 +56,11 @@ class Serega
56
56
  Utils::CheckAllowedKeys.call(delegate_opts, %i[to method allow_nil], :delegate)
57
57
  end
58
58
 
59
- def check_usage_with_other_params(opts, block)
59
+ def check_usage_with_other_params(opts)
60
60
  raise SeregaError, "Option :delegate can not be used together with option :method" if opts.key?(:method)
61
61
  raise SeregaError, "Option :delegate can not be used together with option :const" if opts.key?(:const)
62
62
  raise SeregaError, "Option :delegate can not be used together with option :value" if opts.key?(:value)
63
63
  raise SeregaError, "Option :delegate can not be used together with option :batch" if opts.key?(:batch)
64
- raise SeregaError, "Option :delegate can not be used together with block" if block
65
64
  end
66
65
  end
67
66
  end
@@ -12,28 +12,29 @@ class Serega
12
12
  # Checks attribute :many option
13
13
  #
14
14
  # @param opts [Hash] Attribute options
15
+ # @param block [nil, Proc] Attribute block
15
16
  #
16
17
  # @raise [SeregaError] SeregaError that option has invalid value
17
18
  #
18
19
  # @return [void]
19
20
  #
20
- def call(opts)
21
+ def call(opts, block = nil)
21
22
  return unless opts.key?(:many)
22
23
 
23
- check_many_option_makes_sence(opts)
24
+ check_many_option_makes_sence(opts, block)
24
25
  Utils::CheckOptIsBool.call(opts, :many)
25
26
  end
26
27
 
27
28
  private
28
29
 
29
- def check_many_option_makes_sence(opts)
30
- return if many_option_makes_sence?(opts)
30
+ def check_many_option_makes_sence(opts, block)
31
+ return if many_option_makes_sence?(opts, block)
31
32
 
32
- raise SeregaError, "Option :many can be provided only together with :serializer or :batch option"
33
+ raise SeregaError, "Option :many can be provided only together with :serializer, :batch option or a block"
33
34
  end
34
35
 
35
- def many_option_makes_sence?(opts)
36
- opts[:serializer] || opts[:batch]
36
+ def many_option_makes_sence?(opts, block)
37
+ opts[:serializer] || opts[:batch] || block
37
38
  end
38
39
  end
39
40
  end
@@ -17,20 +17,19 @@ class Serega
17
17
  #
18
18
  # @return [void]
19
19
  #
20
- def call(opts, block = nil)
20
+ def call(opts)
21
21
  return unless opts.key?(:method)
22
22
 
23
- check_usage_with_other_params(opts, block)
23
+ check_usage_with_other_params(opts)
24
24
  Utils::CheckOptIsStringOrSymbol.call(opts, :method)
25
25
  end
26
26
 
27
27
  private
28
28
 
29
- def check_usage_with_other_params(opts, block)
29
+ def check_usage_with_other_params(opts)
30
30
  raise SeregaError, "Option :method can not be used together with option :const" if opts.key?(:const)
31
31
  raise SeregaError, "Option :method can not be used together with option :value" if opts.key?(:value)
32
32
  raise SeregaError, "Option :method can not be used together with option :batch" if opts.key?(:batch)
33
- raise SeregaError, "Option :method can not be used together with block" if block
34
33
  end
35
34
  end
36
35
  end
@@ -12,14 +12,17 @@ class Serega
12
12
  # Checks attribute :serializer option
13
13
  #
14
14
  # @param opts [Hash] Attribute options
15
+ # @param block [nil, Proc] Attribute block (defines a nested serializer)
15
16
  #
16
17
  # @raise [SeregaError] SeregaError that option has invalid value
17
18
  #
18
19
  # @return [void]
19
20
  #
20
- def call(opts)
21
+ def call(opts, block = nil)
21
22
  return unless opts.key?(:serializer)
22
23
 
24
+ raise SeregaError, "Option :serializer can not be used together with block" if block
25
+
23
26
  value = opts[:serializer]
24
27
  return if valid_serializer?(value)
25
28
 
@@ -17,20 +17,19 @@ class Serega
17
17
  #
18
18
  # @return [void]
19
19
  #
20
- def call(opts, block = nil)
20
+ def call(opts)
21
21
  return unless opts.key?(:value)
22
22
 
23
- check_usage_with_other_params(opts, block)
23
+ check_usage_with_other_params(opts)
24
24
 
25
25
  check_value(opts[:value])
26
26
  end
27
27
 
28
28
  private
29
29
 
30
- def check_usage_with_other_params(opts, block)
30
+ def check_usage_with_other_params(opts)
31
31
  raise SeregaError, "Option :value can not be used together with option :method" if opts.key?(:method)
32
32
  raise SeregaError, "Option :value can not be used together with option :const" if opts.key?(:const)
33
- raise SeregaError, "Option :value can not be used together with block" if block
34
33
  end
35
34
 
36
35
  def check_value(value)
@@ -59,15 +59,16 @@ class Serega
59
59
  def check_opts
60
60
  Utils::CheckAllowedKeys.call(opts, allowed_opts_keys, :attribute)
61
61
 
62
- Attribute::CheckOptConst.call(opts, block)
63
- Attribute::CheckOptDelegate.call(opts, block)
62
+ Attribute::CheckOptBaseSerializer.call(opts, block)
63
+ Attribute::CheckOptConst.call(opts)
64
+ Attribute::CheckOptDelegate.call(opts)
64
65
  Attribute::CheckOptHide.call(opts)
65
- Attribute::CheckOptMethod.call(opts, block)
66
- Attribute::CheckOptMany.call(opts)
66
+ Attribute::CheckOptMethod.call(opts)
67
+ Attribute::CheckOptMany.call(opts, block)
67
68
  Attribute::CheckOptPreload.call(opts)
68
- Attribute::CheckOptSerializer.call(opts)
69
- Attribute::CheckOptValue.call(opts, block)
70
- Attribute::CheckOptBatch.call(self.class.serializer_class, opts, block)
69
+ Attribute::CheckOptSerializer.call(opts, block)
70
+ Attribute::CheckOptValue.call(opts)
71
+ Attribute::CheckOptBatch.call(self.class.serializer_class, opts)
71
72
  end
72
73
 
73
74
  def check_block
data/lib/serega.rb CHANGED
@@ -19,6 +19,7 @@ end
19
19
 
20
20
  require_relative "serega/errors"
21
21
  require_relative "serega/helpers/serializer_class_helper"
22
+ require_relative "serega/utils/collection_detector"
22
23
  require_relative "serega/utils/enum_deep_dup"
23
24
  require_relative "serega/utils/enum_deep_freeze"
24
25
  require_relative "serega/utils/method_signature"
@@ -31,16 +32,16 @@ require_relative "serega/attribute_value_resolvers/delegate"
31
32
  require_relative "serega/attribute_value_resolvers/keyword"
32
33
  require_relative "serega/attribute"
33
34
  require_relative "serega/attribute_normalizer"
34
- require_relative "serega/batch/attribute_loader"
35
- require_relative "serega/batch/attribute_loaders"
36
- require_relative "serega/batch/level"
37
- require_relative "serega/batch/loader"
35
+ require_relative "serega/engine/level_queue"
36
+ require_relative "serega/engine/level"
37
+ require_relative "serega/engine/loader"
38
38
  require_relative "serega/validations/utils/check_allowed_keys"
39
39
  require_relative "serega/validations/utils/check_opt_is_bool"
40
40
  require_relative "serega/validations/utils/check_opt_is_hash"
41
41
  require_relative "serega/validations/utils/check_opt_is_string_or_symbol"
42
42
  require_relative "serega/validations/attribute/check_block"
43
43
  require_relative "serega/validations/attribute/check_name"
44
+ require_relative "serega/validations/attribute/check_opt_base_serializer"
44
45
  require_relative "serega/validations/attribute/check_opt_const"
45
46
  require_relative "serega/validations/attribute/check_opt_hide"
46
47
  require_relative "serega/validations/attribute/check_opt_delegate"
@@ -86,15 +87,10 @@ class Serega
86
87
  check_batch_loader_params_class.serializer_class = self
87
88
  const_set(:CheckBatchLoaderParams, check_batch_loader_params_class)
88
89
 
89
- # Assigns `SeregaBatchLoader` constant to current class
90
- batch_loader_class = Class.new(SeregaBatch::Loader)
91
- batch_loader_class.serializer_class = self
92
- const_set(:SeregaBatchLoader, batch_loader_class)
93
-
94
- # Assigns `SeregaBatchAttributeLoader` constant to current class
95
- batch_attribute_loader_class = Class.new(SeregaBatch::AttributeLoader)
96
- batch_attribute_loader_class.serializer_class = self
97
- const_set(:SeregaBatchAttributeLoader, batch_attribute_loader_class)
90
+ # Assigns `SeregaEngineLoader` constant to current class
91
+ engine_loader_class = Class.new(SeregaEngine::Loader)
92
+ engine_loader_class.serializer_class = self
93
+ const_set(:SeregaEngineLoader, engine_loader_class)
98
94
 
99
95
  #
100
96
  # Serializers class methods
@@ -174,7 +170,19 @@ class Serega
174
170
  #
175
171
  # @param name [Symbol] Attribute name. Attribute value will be found by executing `object.<name>`
176
172
  # @param opts [Hash] Options to serialize attribute
177
- # @param block [Proc] Custom block to find attribute value. Accepts object and context.
173
+ # @param block [Proc] Defines attributes of a nested anonymous serializer.
174
+ # The block is executed in the context of that serializer, so everything
175
+ # available in a serializer class body can be used inside. The attribute
176
+ # value (found by name/:method/:value/:delegate/:const/:batch as usual)
177
+ # is serialized with this nested serializer. The nested serializer
178
+ # inherits from the `:base_serializer` attribute option or
179
+ # `config.base_serializer` (one of them is required).
180
+ #
181
+ # @example
182
+ # attribute :statistics, method: :itself do
183
+ # attribute :likes_count
184
+ # attribute :comments_count
185
+ # end
178
186
  #
179
187
  # @return [Serega::SeregaAttribute] Added attribute
180
188
  #
@@ -214,7 +222,7 @@ class Serega
214
222
  def batch(name, value = nil, &block)
215
223
  raise SeregaError, "Batch loader must be defined with a callable value or block" if (value && block) || (!value && !block)
216
224
 
217
- batch_loader = self::SeregaBatchLoader.new(name: name, block: value || block)
225
+ batch_loader = self::SeregaEngineLoader.new(name: name, block: value || block)
218
226
  batch_loaders[batch_loader.name] = batch_loader
219
227
  end
220
228
 
@@ -260,7 +268,7 @@ class Serega
260
268
  # @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
261
269
  # @option opts [Boolean] :validate Validates provided modifiers (Default is true)
262
270
  # @option opts [Hash] :context Serialization context
263
- # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
271
+ # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable) && !object.is_a?(Hash) && !object.is_a?(Struct)`)
264
272
  #
265
273
  # @return [Hash] Serialization result
266
274
  #
@@ -281,7 +289,7 @@ class Serega
281
289
  # @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
282
290
  # @option opts [Boolean] :validate Validates provided modifiers (Default is true)
283
291
  # @option opts [Hash] :context Serialization context
284
- # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
292
+ # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable) && !object.is_a?(Hash) && !object.is_a?(Struct)`)
285
293
  #
286
294
  # @return [Data, Array<Data>, nil] Serialization result as Data object(s)
287
295
  #
@@ -333,13 +341,9 @@ class Serega
333
341
  plan_point_class.serializer_class = subclass
334
342
  subclass.const_set(:SeregaPlanPoint, plan_point_class)
335
343
 
336
- batch_loader_class = Class.new(self::SeregaBatchLoader)
337
- batch_loader_class.serializer_class = subclass
338
- subclass.const_set(:SeregaBatchLoader, batch_loader_class)
339
-
340
- batch_attribute_loader_class = Class.new(self::SeregaBatchAttributeLoader)
341
- batch_attribute_loader_class.serializer_class = subclass
342
- subclass.const_set(:SeregaBatchAttributeLoader, batch_attribute_loader_class)
344
+ engine_loader_class = Class.new(self::SeregaEngineLoader)
345
+ engine_loader_class.serializer_class = subclass
346
+ subclass.const_set(:SeregaEngineLoader, engine_loader_class)
343
347
 
344
348
  object_serializer_class = Class.new(self::SeregaObjectSerializer)
345
349
  object_serializer_class.serializer_class = subclass
@@ -419,7 +423,7 @@ class Serega
419
423
  # @param object [Object] Serialized object
420
424
  # @param opts [Hash, nil] Serializing options
421
425
  # @option opts [Hash] :context Serialization context
422
- # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
426
+ # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable) && !object.is_a?(Hash) && !object.is_a?(Struct)`)
423
427
  #
424
428
  # @return [Hash] Serialization result
425
429
  #
@@ -441,7 +445,7 @@ class Serega
441
445
  # @param object [Object] Serialized object
442
446
  # @param opts [Hash, nil] Serializing options
443
447
  # @option opts [Hash] :context Serialization context
444
- # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
448
+ # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable) && !object.is_a?(Hash) && !object.is_a?(Struct)`)
445
449
  #
446
450
  # @return [Data] Serialization result
447
451
  #
@@ -481,8 +485,8 @@ class Serega
481
485
  self.class::CheckSerializeParams.new(opts).validate unless opts.empty?
482
486
 
483
487
  opts[:context] ||= {}
484
- opts[:batch_loaders] = SeregaBatch::AttributeLoaders.new(opts[:context])
485
- opts[:many] = object.is_a?(Enumerable) unless opts.key?(:many)
488
+ opts[:level_queue] = SeregaEngine::LevelQueue.new
489
+ opts[:many] = SeregaUtils::CollectionDetector.call(object) unless opts.key?(:many)
486
490
  opts[:plan] = plan
487
491
  opts
488
492
  end
@@ -493,7 +497,7 @@ class Serega
493
497
  # - plugin :metadata (adds metadata to final result)
494
498
  def serialize(object, opts)
495
499
  result = self.class::SeregaObjectSerializer.new(**opts).serialize(object)
496
- opts[:batch_loaders].load_all
500
+ opts[:level_queue].run
497
501
  result
498
502
  end
499
503
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serega
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.2
4
+ version: 0.39.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Glushkov
@@ -33,12 +33,11 @@ files:
33
33
  - lib/serega/attribute_value_resolvers/const.rb
34
34
  - lib/serega/attribute_value_resolvers/delegate.rb
35
35
  - lib/serega/attribute_value_resolvers/keyword.rb
36
- - lib/serega/batch/attribute_loader.rb
37
- - lib/serega/batch/attribute_loaders.rb
38
- - lib/serega/batch/level.rb
39
- - lib/serega/batch/loader.rb
40
36
  - lib/serega/config.rb
41
37
  - lib/serega/data_builder.rb
38
+ - lib/serega/engine/level.rb
39
+ - lib/serega/engine/level_queue.rb
40
+ - lib/serega/engine/loader.rb
42
41
  - lib/serega/errors.rb
43
42
  - lib/serega/helpers/serializer_class_helper.rb
44
43
  - lib/serega/object_serializer.rb
@@ -71,6 +70,7 @@ files:
71
70
  - lib/serega/plugins/root/root.rb
72
71
  - lib/serega/plugins/string_modifiers/parse_string_modifiers.rb
73
72
  - lib/serega/plugins/string_modifiers/string_modifiers.rb
73
+ - lib/serega/utils/collection_detector.rb
74
74
  - lib/serega/utils/enum_deep_dup.rb
75
75
  - lib/serega/utils/enum_deep_freeze.rb
76
76
  - lib/serega/utils/method_signature.rb
@@ -79,6 +79,7 @@ files:
79
79
  - lib/serega/utils/to_hash.rb
80
80
  - lib/serega/validations/attribute/check_block.rb
81
81
  - lib/serega/validations/attribute/check_name.rb
82
+ - lib/serega/validations/attribute/check_opt_base_serializer.rb
82
83
  - lib/serega/validations/attribute/check_opt_batch.rb
83
84
  - lib/serega/validations/attribute/check_opt_const.rb
84
85
  - lib/serega/validations/attribute/check_opt_delegate.rb
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- #
5
- # Batch feature main module
6
- #
7
- module SeregaBatch
8
- # Remembers data required to batch load single attribute
9
- class AttributeLoader
10
- # Instance methods for AttributeLoader
11
- module InstanceMethods
12
- # @param point [SeregaPlanPoint]
13
- # @param level [SeregaBatch::Level] Shared objects and results for this point's plan level
14
- def initialize(point, level)
15
- @point = point
16
- @level = level
17
- @serialized_object_attachers = []
18
- end
19
-
20
- # Stores object with attacher to find and attach attribute values in batch later.
21
- # @param object [Object] Serialized object
22
- # @param attacher [Proc] Proc that attaches found values to originated attributes
23
- # @return [void]
24
- def store(object, attacher)
25
- serialized_object_attachers << [object, attacher]
26
- end
27
-
28
- # Preloads this attribute's associations onto the level's objects, once for
29
- # the attribute (not per named batch loader), via the serializer's registered
30
- # `preload_with` handler. Raises when `:preload` is declared but no handler
31
- # exists, so a declared preload never silently does nothing.
32
- # @return [void]
33
- def preload_associations
34
- preloads = point.attribute.preloads
35
- return unless preloads
36
-
37
- handler = point.class.serializer_class.preload_with
38
- unless handler
39
- raise SeregaError, "The :preload option requires a preload handler. Register one with `preload_with` (the :activerecord_preloads plugin does this for you)."
40
- end
41
-
42
- handler.call(level.objects, preloads)
43
- rescue => error
44
- SeregaUtils::SerializedAttributeError.call(error, point)
45
- end
46
-
47
- # Loads a single named batch for this level's objects. Caching across
48
- # attributes that reuse the loader is handled by the Level.
49
- # @param loader [SeregaBatch::Loader] Named batch loader
50
- # @return [Object] Loaded batch values
51
- def load_batch(loader)
52
- level.load(loader)
53
- rescue => error
54
- SeregaUtils::SerializedAttributeError.call(error, point)
55
- end
56
-
57
- # Attaches already loaded batch values to every stored object.
58
- # Value resolution happens here (inside the attacher), so errors are
59
- # annotated with the attribute, matching the synchronous walk.
60
- # @param batches [Hash] Loaded values grouped by batch loader name
61
- # @return [void]
62
- def attach(batches)
63
- serialized_object_attachers.each do |object, attacher|
64
- attacher.call(object, batches)
65
- end
66
- rescue => error
67
- SeregaUtils::SerializedAttributeError.call(error, point)
68
- end
69
-
70
- attr_reader :point
71
- attr_reader :level
72
-
73
- private
74
-
75
- attr_reader :serialized_object_attachers
76
- end
77
-
78
- extend SeregaHelpers::SerializerClassHelper
79
- include InstanceMethods
80
- end
81
- end
82
- end
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- #
5
- # Batch feature main module
6
- #
7
- module SeregaBatch
8
- # Reserved batch-loader name that marks relation/preload attributes as
9
- # batch-processed. It has no registered loader — the attribute's value comes
10
- # from its own resolver — so `load_batches` skips it (nothing to load).
11
- AUTO_BATCH_LOADER_NAME = :__auto_batch__
12
-
13
- #
14
- # Batch loaders
15
- #
16
- # Remembers data required to batch load all attributes
17
- class AttributeLoaders
18
- #
19
- # Initializes new batch loaders
20
- #
21
- # @param context [Hash] Serialization context, constant for the whole run
22
- #
23
- def initialize(context)
24
- @context = context
25
- @attribute_loaders = []
26
- @point_index = {}.compare_by_identity
27
- @levels = {}.compare_by_identity
28
- end
29
-
30
- # Gathers a chunk of objects serialized at a plan level, so every attribute at
31
- # that level shares one set of objects to batch load against.
32
- #
33
- # @param plan [SeregaPlan] Plan serializing these objects
34
- # @param objects [Array] Objects being serialized at this level
35
- #
36
- # @return [void]
37
- def add_objects(plan, objects)
38
- level_for(plan).add_objects(objects)
39
- end
40
-
41
- # Remembers data for batch serialization:
42
- #
43
- # @param point [SeregaPlanPoint] Serialization plan point
44
- # @param object [Object] Serialized object
45
- # @param attacher [Proc] Serialized value attacher
46
- #
47
- # @return [void]
48
- def remember(point, object, attacher)
49
- attribute_loader = point_index[point]
50
-
51
- unless attribute_loader
52
- serializer_class = point.class.serializer_class
53
- attribute_loader = serializer_class::SeregaBatchAttributeLoader.new(point, level_for(point.plan))
54
- attribute_loaders << attribute_loader
55
- point_index[point] = attribute_loader
56
- end
57
-
58
- attribute_loader.store(object, attacher)
59
- end
60
-
61
- #
62
- # Loads all registered batches and attaches loaded values.
63
- #
64
- # Iterates over each loader including loaders added during iteration
65
- # (child serializers discovered inside a batch flush add new loaders).
66
- def load_all
67
- i = 0
68
- while i < attribute_loaders.size
69
- attribute_loader = attribute_loaders[i]
70
- attribute_loader.attach(load_batches(attribute_loader))
71
- i += 1
72
- end
73
- end
74
-
75
- private
76
-
77
- # Serialization context shared by every level
78
- attr_reader :context
79
-
80
- # keeps all AttributeLoader instances
81
- attr_reader :attribute_loaders
82
-
83
- # keeps tracking of already added points
84
- attr_reader :point_index
85
-
86
- # keeps one Level per plan (compare_by_identity: plan instance identifies a level)
87
- attr_reader :levels
88
-
89
- def level_for(plan)
90
- levels[plan] ||= Level.new(context)
91
- end
92
-
93
- # Builds the { batch_loader_name => loaded_values } hash for one attribute.
94
- #
95
- # Loading is delegated to the attribute's Level, which loads each named batch
96
- # once for the whole level: attributes sharing a loader over the same objects
97
- # reuse a single result, deeper levels load separately.
98
- def load_batches(attribute_loader)
99
- attribute_loader.preload_associations
100
-
101
- point = attribute_loader.point
102
- serializer_class = point.class.serializer_class
103
-
104
- batches = {}
105
- point.batch_loaders.each do |batch_loader_name|
106
- # Auto-batched relations/preloads carry the marker, not a real loader:
107
- # their value comes from their own resolver, so there is nothing to load.
108
- next if batch_loader_name == AUTO_BATCH_LOADER_NAME
109
-
110
- loader = serializer_class.batch_loaders[batch_loader_name]
111
- batches[batch_loader_name] = attribute_loader.load_batch(loader)
112
- end
113
- batches
114
- end
115
- end
116
- end
117
- end