serega 0.21.0 → 0.30.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -229
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +31 -3
  5. data/lib/serega/attribute_normalizer.rb +143 -42
  6. data/lib/serega/batch/attribute_loader.rb +70 -0
  7. data/lib/serega/batch/attribute_loaders.rb +59 -0
  8. data/lib/serega/batch/auto_resolver.rb +24 -0
  9. data/lib/serega/batch/auto_resolver_factory.rb +48 -0
  10. data/lib/serega/batch/loader.rb +67 -0
  11. data/lib/serega/config.rb +59 -1
  12. data/lib/serega/object_serializer.rb +11 -9
  13. data/lib/serega/plan.rb +16 -0
  14. data/lib/serega/plan_point.rb +31 -5
  15. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +25 -13
  16. data/lib/serega/plugins/activerecord_preloads/lib/active_record_objects.rb +31 -0
  17. data/lib/serega/plugins/camel_case/camel_case.rb +1 -1
  18. data/lib/serega/plugins/formatters/formatters.rb +79 -22
  19. data/lib/serega/plugins/if/if.rb +41 -22
  20. data/lib/serega/plugins/if/validations/check_opt_if.rb +27 -6
  21. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +27 -6
  22. data/lib/serega/plugins/if/validations/check_opt_unless.rb +27 -6
  23. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +27 -6
  24. data/lib/serega/plugins/metadata/meta_attribute.rb +9 -10
  25. data/lib/serega/plugins/metadata/validations/check_block.rb +2 -4
  26. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +2 -3
  27. data/lib/serega/plugins/presenter/presenter.rb +1 -1
  28. data/lib/serega/utils/format_user_preloads.rb +58 -0
  29. data/lib/serega/utils/method_signature.rb +89 -0
  30. data/lib/serega/utils/preload_paths.rb +53 -0
  31. data/lib/serega/utils/preloads_constructor.rb +77 -0
  32. data/lib/serega/validations/attribute/check_block.rb +38 -6
  33. data/lib/serega/validations/attribute/check_opt_batch.rb +101 -0
  34. data/lib/serega/validations/attribute/check_opt_const.rb +1 -0
  35. data/lib/serega/validations/attribute/check_opt_delegate.rb +1 -0
  36. data/lib/serega/validations/attribute/check_opt_method.rb +1 -0
  37. data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload.rb +2 -2
  38. data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload_path.rb +3 -3
  39. data/lib/serega/validations/attribute/check_opt_value.rb +35 -9
  40. data/lib/serega/validations/check_attribute_params.rb +3 -2
  41. data/lib/serega/validations/check_batch_loader_params.rb +80 -0
  42. data/lib/serega/validations/initiate/check_modifiers.rb +1 -1
  43. data/lib/serega.rb +98 -7
  44. metadata +17 -37
  45. data/lib/serega/plugins/batch/batch.rb +0 -168
  46. data/lib/serega/plugins/batch/lib/batch_config.rb +0 -77
  47. data/lib/serega/plugins/batch/lib/loader.rb +0 -113
  48. data/lib/serega/plugins/batch/lib/loaders.rb +0 -45
  49. data/lib/serega/plugins/batch/lib/modules/attribute.rb +0 -26
  50. data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +0 -78
  51. data/lib/serega/plugins/batch/lib/modules/check_attribute_params.rb +0 -22
  52. data/lib/serega/plugins/batch/lib/modules/config.rb +0 -23
  53. data/lib/serega/plugins/batch/lib/modules/object_serializer.rb +0 -46
  54. data/lib/serega/plugins/batch/lib/modules/plan_point.rb +0 -22
  55. data/lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb +0 -43
  56. data/lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb +0 -54
  57. data/lib/serega/plugins/batch/lib/plugins_extensions/if.rb +0 -31
  58. data/lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb +0 -34
  59. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb +0 -43
  60. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +0 -59
  61. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +0 -62
  62. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +0 -60
  63. data/lib/serega/plugins/preloads/lib/modules/attribute.rb +0 -28
  64. data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +0 -99
  65. data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +0 -22
  66. data/lib/serega/plugins/preloads/lib/modules/config.rb +0 -19
  67. data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +0 -41
  68. data/lib/serega/plugins/preloads/lib/preload_paths.rb +0 -53
  69. data/lib/serega/plugins/preloads/lib/preloads_config.rb +0 -62
  70. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +0 -79
  71. data/lib/serega/plugins/preloads/preloads.rb +0 -162
  72. data/lib/serega/utils/params_count.rb +0 -50
  73. data/lib/serega/validations/utils/check_extra_keyword_arg.rb +0 -33
@@ -54,6 +54,15 @@ class Serega
54
54
  @value_block ||= prepare_value_block
55
55
  end
56
56
 
57
+ #
58
+ # Detects value block parameters signature
59
+ #
60
+ # @return [String] value block parameters signature
61
+ #
62
+ def value_block_signature
63
+ @value_block_signature ||= SeregaUtils::MethodSignature.call(value_block, pos_limit: 2, keyword_args: [:ctx])
64
+ end
65
+
57
66
  #
58
67
  # Shows if attribute is specified to be hidden
59
68
  #
@@ -99,34 +108,72 @@ class Serega
99
108
  @default = prepare_default
100
109
  end
101
110
 
102
- private
111
+ #
112
+ # Shows normalized preloads for current attribute
113
+ #
114
+ # @return [Hash, nil] normalized preloads of current attribute
115
+ #
116
+ def preloads
117
+ return @preloads if instance_variable_defined?(:@preloads)
103
118
 
104
- def prepare_name
105
- init_name.to_sym
119
+ @preloads = prepare_preloads
106
120
  end
107
121
 
108
122
  #
109
- # Patched in:
110
- # - plugin :formatters (wraps resulted block in formatter block and formats :const values)
123
+ # Shows normalized preloads_path for current attribute
111
124
  #
112
- def prepare_value_block
113
- value_block =
114
- prepare_init_block ||
115
- prepare_value_option_block ||
116
- prepare_const_block ||
117
- prepare_delegate_block ||
118
- prepare_keyword_block
125
+ # @return [Array] normalized preloads_path of current attribute
126
+ #
127
+ def preloads_path
128
+ return @preloads_path if instance_variable_defined?(:@preloads_path)
119
129
 
120
- prepare_value_block_with_default(value_block)
130
+ @preloads_path = prepare_preloads_path
121
131
  end
122
132
 
133
+ # Shows specified batch loaders names
134
+ # @return [Array<Symbol>] specified serializer
123
135
  #
124
- # Patched in:
125
- # - plugin :preloads (returns true by default if config option auto_hide_attribute_with_preloads is enabled)
126
- # - plugin :batch (returns true by default if auto_hide option was set and attribute has batch loader)
127
- #
136
+ def batch_loaders
137
+ @batch_loaders ||= prepare_batch_loaders
138
+ end
139
+
140
+ private
141
+
142
+ def prepare_name
143
+ init_name.to_sym
144
+ end
145
+
146
+ def prepare_value_block
147
+ init_block \
148
+ || init_opts[:value] \
149
+ || prepare_const_block \
150
+ || prepare_delegate_block \
151
+ || prepare_batch_loader_block \
152
+ || prepare_keyword_block
153
+ end
154
+
128
155
  def prepare_hide
129
- init_opts[:hide]
156
+ # Return provided durectly value
157
+ hide = init_opts[:hide]
158
+ return hide if (hide == true) || (hide == false)
159
+
160
+ # Auto hide when `:preload` option provided
161
+ if config.auto_hide.fetch(:has_preload_option)
162
+ return true if preloads
163
+ end
164
+
165
+ # Auto hide when `:batch` option provided
166
+ if config.auto_hide.fetch(:has_batch_option)
167
+ return true if batch_loaders.any?
168
+ end
169
+
170
+ # Return nil for undefined value which means "not hide" but allows
171
+ # to change this value by plugins
172
+ nil
173
+ end
174
+
175
+ def config
176
+ self.class.serializer_class.config
130
177
  end
131
178
 
132
179
  def prepare_many
@@ -155,35 +202,30 @@ class Serega
155
202
  end
156
203
  end
157
204
 
158
- def prepare_init_block
159
- prepare_callable_proc(init_block)
160
- end
205
+ def prepare_batch_loader_block
206
+ batch_opt = init_opts[:batch]
207
+ return unless batch_opt
161
208
 
162
- def prepare_value_option_block
163
- prepare_callable_proc(init_opts[:value])
209
+ SeregaBatch::AutoResolverFactory.get(self.class.serializer_class, name, batch_opt)
164
210
  end
165
211
 
166
- def prepare_callable_proc(callable)
167
- return unless callable
168
-
169
- params_count = SeregaUtils::ParamsCount.call(callable, max_count: 2)
170
- case params_count
171
- when 0 then proc { |obj, _ctx| callable.call }
172
- when 1 then proc { |obj, _ctx| callable.call(obj) }
173
- else callable
212
+ def prepare_batch_loaders
213
+ batch_opt = init_opts[:batch]
214
+
215
+ if batch_opt.nil?
216
+ []
217
+ elsif batch_opt == true || batch_opt.respond_to?(:call)
218
+ [name]
219
+ elsif batch_opt.is_a?(Symbol)
220
+ [batch_opt]
221
+ elsif batch_opt.is_a?(String)
222
+ [batch_opt.to_sym]
223
+ else
224
+ use_opt = batch_opt.fetch(:use)
225
+ use_opt.respond_to?(:call) ? [name] : Array(use_opt).map(&:to_sym)
174
226
  end
175
227
  end
176
228
 
177
- def prepare_value_block_with_default(callable)
178
- default_value = default
179
- return callable if default_value.nil?
180
-
181
- proc { |obj, ctx|
182
- res = callable.call(obj, ctx)
183
- res.nil? ? default_value : res
184
- }
185
- end
186
-
187
229
  def prepare_default
188
230
  init_opts.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
189
231
  end
@@ -195,7 +237,7 @@ class Serega
195
237
  key_method_name = delegate[:method] || method_name
196
238
  delegate_to = delegate[:to]
197
239
 
198
- allow_nil = delegate.fetch(:allow_nil) { self.class.serializer_class.config.delegate_default_allow_nil }
240
+ allow_nil = delegate.fetch(:allow_nil) { config.delegate_default_allow_nil }
199
241
 
200
242
  if allow_nil
201
243
  proc do |object|
@@ -207,6 +249,65 @@ class Serega
207
249
  end
208
250
  end
209
251
  end
252
+
253
+ # Prepares preloads
254
+ # @return [Hash,nil]
255
+ # - Nil means no need to add preloads and nested preloads
256
+ # - Hash with any keys will add preloads
257
+ # - Empty hash will skip only top-level preloads, but will allow to load nested preloads
258
+ def prepare_preloads
259
+ preload = init_opts[:preload]
260
+
261
+ # Handle explicit preload option
262
+ if init_opts.key?(:preload)
263
+ return nil unless preload # return nil when false or nil
264
+ return SeregaUtils::FormatUserPreloads.call(preload)
265
+ end
266
+
267
+ # Auto-preload for delegate
268
+ if init_opts[:delegate] && config.auto_preload.fetch(:has_delegate_option)
269
+ delegate_to = init_opts[:delegate][:to]
270
+ return SeregaUtils::FormatUserPreloads.call(delegate_to)
271
+ end
272
+
273
+ # Auto-preload for serializer
274
+ if init_opts[:serializer] && config.auto_preload.fetch(:has_serializer_option)
275
+ return SeregaUtils::FormatUserPreloads.call(name)
276
+ end
277
+
278
+ nil
279
+ end
280
+
281
+ def prepare_preloads_path
282
+ path = init_opts.fetch(:preload_path) { default_preload_path(preloads) }
283
+
284
+ if path && path[0].is_a?(Array)
285
+ prepare_many_preload_paths(path)
286
+ else
287
+ prepare_one_preload_path(path)
288
+ end
289
+ end
290
+
291
+ def prepare_one_preload_path(path)
292
+ return unless path
293
+
294
+ case path
295
+ when Array
296
+ path.map(&:to_sym).freeze
297
+ else
298
+ [path.to_sym].freeze
299
+ end
300
+ end
301
+
302
+ def prepare_many_preload_paths(paths)
303
+ paths.map { |path| prepare_one_preload_path(path) }.freeze
304
+ end
305
+
306
+ def default_preload_path(preloads)
307
+ return FROZEN_EMPTY_ARRAY if !preloads || preloads.empty?
308
+
309
+ [preloads.keys.first]
310
+ end
210
311
  end
211
312
 
212
313
  extend Serega::SeregaHelpers::SerializerClassHelper
@@ -0,0 +1,70 @@
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
+ def initialize(point)
14
+ @point = point
15
+ @objects = []
16
+ @serialized_object_attachers = {}
17
+ end
18
+
19
+ # Stores object with attacher to find and attach attribute values in batch later.
20
+ # @param object [Object] Serialized object
21
+ # @param attacher [Proc] Proc that attaches found values to originated attributes
22
+ # @return [void]
23
+ def store(object, attacher)
24
+ objects << object
25
+ serialized_object_attachers[object] = attacher
26
+ end
27
+
28
+ # Loads serialized values for all stored objects for current attribute
29
+ # @param context [Hash] Current serialization context
30
+ # @return [void]
31
+ def load_all(context)
32
+ batches = {}
33
+ serializer_class = point.class.serializer_class
34
+
35
+ point.batch_loaders.each do |batch_loader_name|
36
+ batches[batch_loader_name] ||= load_one(serializer_class, batch_loader_name, context)
37
+ end
38
+
39
+ serialized_object_attachers.each do |object, attacher|
40
+ attacher.call(object, batches)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Patched in:
47
+ # - plugin :activerecord_preloads (preloads associations to found AR objects)
48
+ def load_one(serializer_class, batch_loader_name, context)
49
+ serializer_class.batch_loaders[batch_loader_name].load(objects, context)
50
+ rescue => error
51
+ reraise_with_serialized_attribute_details(error)
52
+ end
53
+
54
+ def reraise_with_serialized_attribute_details(error)
55
+ raise error.exception(<<~MESSAGE.strip)
56
+ #{error.message}
57
+ (when serializing '#{point.name}' attribute in #{point.class.serializer_class})
58
+ MESSAGE
59
+ end
60
+
61
+ attr_reader :point
62
+ attr_reader :objects
63
+ attr_reader :serialized_object_attachers
64
+ end
65
+
66
+ extend SeregaHelpers::SerializerClassHelper
67
+ include InstanceMethods
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Batch feature main module
6
+ #
7
+ module SeregaBatch
8
+ #
9
+ # Batch loaders
10
+ #
11
+ # Remembers data required to batch load all attributes
12
+ class AttributeLoaders
13
+ #
14
+ # Initializes new batch loaders
15
+ #
16
+ def initialize
17
+ @point_batch_loaders = []
18
+ @point_index = {}.compare_by_identity
19
+ end
20
+
21
+ # Remembers data for batch seryalization:
22
+ #
23
+ # @param point [SeregaPlanPoint] Serialization plan point
24
+ # @param object [Object] Serialized object
25
+ # @param attacher [Proc] Serialized value attacher
26
+ #
27
+ # @return [void]
28
+ def remember(point, object, attacher)
29
+ point_batch_loader = point_index[point]
30
+
31
+ unless point_batch_loader
32
+ serializer_class = point.class.serializer_class
33
+ point_batch_loader = serializer_class::SeregaBatchAttributeLoader.new(point)
34
+ point_batch_loaders << point_batch_loader
35
+ point_index[point] = point_batch_loader
36
+ end
37
+
38
+ point_batch_loader.store(object, attacher)
39
+ end
40
+
41
+ #
42
+ # Loads all registered batches and removes them from registered list
43
+ #
44
+ def load_all(context)
45
+ point_batch_loaders.each do |point_batch_loader|
46
+ point_batch_loader.load_all(context)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # keeps all point_batch_loaders list
53
+ attr_reader :point_batch_loaders
54
+
55
+ # keeps tracking of already added serializers
56
+ attr_reader :point_index
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaBatch
5
+ #
6
+ # Automatically generated resolver for batch_loader
7
+ #
8
+ class AutoResolver
9
+ attr_reader :loader_name
10
+ attr_reader :id_method
11
+
12
+ def initialize(loader_name, id_method)
13
+ @loader_name = loader_name
14
+ @id_method = id_method
15
+ end
16
+
17
+ # Finds object attribute value from hash of batch_loaded values for all
18
+ # serialized objects
19
+ def call(obj, batches:)
20
+ batches.fetch(loader_name)[obj.public_send(id_method)]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaBatch
5
+ #
6
+ # Factory generates callable object that should be able to take
7
+ # batch loaded results, current object, and find attribute value for this
8
+ # object
9
+ #
10
+ class AutoResolverFactory
11
+ #
12
+ # Generates callable block to find attribute value when attribute with :batch
13
+ # option has no block or manual :value option.
14
+ #
15
+ # It handles this cases:
16
+ # - `attribute :foo, batch: true`
17
+ # - `attribute :foo, batch: FooLoader`
18
+ # - `attribute :foo, batch: { id: :foo_id }`
19
+ # - `attribute :foo, batch: { use: FooLoader, id: foo_id }`
20
+ # - `attribute :foo, batch: { use: :foo_loader, id: foo_id }`
21
+ #
22
+ # In other cases we should never call tis method here.
23
+ #
24
+ def self.get(serializer_class, attribute_name, batch_opt)
25
+ if batch_opt == true # ex: `batch: true`
26
+ loader_name = attribute_name
27
+ loader_id_method = :id
28
+ elsif batch_opt.respond_to?(:call) # ex: `batch: FooLoader`
29
+ serializer_class.batch_loader(attribute_name, batch_opt)
30
+ loader_name = attribute_name
31
+ loader_id_method = :id
32
+ else
33
+ use = batch_opt[:use]
34
+ loader_id_method = batch_opt[:id] || :id
35
+
36
+ if use.respond_to?(:call) # ex: `batch: { use: FooLoader }`
37
+ loader_name = attribute_name
38
+ serializer_class.batch_loader(loader_name, use)
39
+ else # ex: `batch: { use: :foo }` || batch: { id: :some_id }
40
+ loader_name = use || attribute_name
41
+ end
42
+ end
43
+
44
+ AutoResolver.new(loader_name, loader_id_method)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Batch feature main module
6
+ #
7
+ module SeregaBatch
8
+ #
9
+ # Batch loader
10
+ #
11
+ class Loader
12
+ #
13
+ # BatchLoader instance methods
14
+ #
15
+ module InstanceMethods
16
+ # BatchLoader initial params
17
+ # @return [Hash] BatchLoader initial params
18
+ attr_reader :initials
19
+
20
+ # BatchLoader name
21
+ # @return [Symbol] BatchLoader name
22
+ attr_reader :name
23
+
24
+ # BatchLoader block
25
+ # @return [#call] BatchLoader block
26
+ attr_reader :block
27
+
28
+ #
29
+ # Initializes new batch loader
30
+ #
31
+ # @param name [Symbol, String] Name of attribute
32
+ # @param block [#call] BatchLoader block
33
+ #
34
+ def initialize(name:, block:)
35
+ serializer_class = self.class.serializer_class
36
+ serializer_class::CheckBatchLoaderParams.new(name, block).validate
37
+
38
+ @initials = SeregaUtils::EnumDeepFreeze.call(name: name, block: block)
39
+ @name = name.to_sym
40
+ @block = block
41
+ @signature = SeregaUtils::MethodSignature.call(block, pos_limit: 2, keyword_args: [:ctx])
42
+ end
43
+
44
+ # Serializes values for objects
45
+ # @param objects [Array] Serialized objects
46
+ # @param context [Hash] Serialization context
47
+ # @return [void]
48
+ def load(objects, context)
49
+ case signature
50
+ when "1" then block.call(objects)
51
+ when "2" then block.call(objects, context)
52
+ else block.call(objects, ctx: context) # "1_ctx"
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ # BatchLoader block signature
59
+ # @return [String] BatchLoader block signature
60
+ attr_reader :signature
61
+ end
62
+
63
+ extend SeregaHelpers::SerializerClassHelper
64
+ include InstanceMethods
65
+ end
66
+ end
67
+ end
data/lib/serega/config.rb CHANGED
@@ -13,12 +13,30 @@ class Serega
13
13
  DEFAULTS = {
14
14
  plugins: [],
15
15
  initiate_keys: %i[only with except check_initiate_params].freeze,
16
- attribute_keys: %i[method value serializer many hide const delegate default].freeze,
16
+ attribute_keys: %i[
17
+ method
18
+ value
19
+ serializer
20
+ many
21
+ hide
22
+ const
23
+ delegate
24
+ default
25
+ preload
26
+ preload_path
27
+ batch
28
+ ].freeze,
17
29
  serialize_keys: %i[context many].freeze,
18
30
  check_attribute_name: true,
19
31
  check_initiate_params: true,
20
32
  delegate_default_allow_nil: false,
21
33
  max_cached_plans_per_serializer_count: 0,
34
+ auto_preload: {has_delegate_option: false, has_serializer_option: false},
35
+ auto_hide: {has_preload_option: false, has_batch_option: false},
36
+ # auto_preload_attributes_with_delegate: false,
37
+ # auto_preload_attributes_with_serializer: false,
38
+ # auto_hide_attributes_with_preload: false,
39
+ # hide_batch_attributes: false,
22
40
  to_json: (SeregaJSON.adapter == :oj) ? SeregaJSON::OjDump : SeregaJSON::JSONDump,
23
41
  from_json: (SeregaJSON.adapter == :oj) ? SeregaJSON::OjLoad : SeregaJSON::JSONLoad
24
42
  }.freeze
@@ -102,6 +120,46 @@ class Serega
102
120
  opts[:delegate_default_allow_nil] = value
103
121
  end
104
122
 
123
+ # @return [Hash] auto_hide option
124
+ def auto_hide
125
+ opts.fetch(:auto_hide)
126
+ end
127
+
128
+ # Validates and sets auto_hide option
129
+ # @return [Hash] New auto_hide option with attributes that trigger auto hide
130
+ def auto_hide=(value)
131
+ opts[:auto_hide] =
132
+ case value
133
+ when true then {has_preload_option: true, has_batch_option: true}
134
+ when false then {has_preload_option: false, has_batch_option: false}
135
+ when Hash
136
+ SeregaValidations::Utils::CheckAllowedKeys.call(value, %i[has_preload_option has_batch_option], "auto_hide")
137
+ {has_preload_option: !!value[:has_preload_option], has_batch_option: !!value[:has_batch_option]}
138
+ else
139
+ raise SeregaError, "Must have boolean value or Hash, #{value.inspect} provided"
140
+ end
141
+ end
142
+
143
+ # @return [Hash] auto_preload option
144
+ def auto_preload
145
+ opts.fetch(:auto_preload)
146
+ end
147
+
148
+ # Validates and sets auto_preload option
149
+ # @return [Hash] New auto_preload option with attributes that trigger auto preload
150
+ def auto_preload=(value)
151
+ opts[:auto_preload] =
152
+ case value
153
+ when true then {has_delegate_option: true, has_serializer_option: true}
154
+ when false then {has_delegate_option: false, has_serializer_option: false}
155
+ when Hash
156
+ SeregaValidations::Utils::CheckAllowedKeys.call(value, %i[has_delegate_option has_serializer_option], "auto_preload")
157
+ {has_delegate_option: !!value[:has_delegate_option], has_serializer_option: !!value[:has_serializer_option]}
158
+ else
159
+ raise SeregaError, "Must have boolean value or Hash, #{value.inspect} provided"
160
+ end
161
+ end
162
+
105
163
  # Returns :max_cached_plans_per_serializer_count config option
106
164
  # @return [Boolean] Current :max_cached_plans_per_serializer_count config option
107
165
  def max_cached_plans_per_serializer_count
@@ -10,7 +10,7 @@ class Serega
10
10
  # SeregaObjectSerializer instance methods
11
11
  #
12
12
  module InstanceMethods
13
- attr_reader :context, :plan, :many, :opts
13
+ attr_reader :context, :plan, :many, :opts, :batch_loaders
14
14
 
15
15
  # @param plan [SeregaPlan] Serialization plan
16
16
  # @param context [Hash] Serialization context
@@ -23,6 +23,7 @@ class Serega
23
23
  @plan = plan
24
24
  @many = many
25
25
  @opts = opts
26
+ @batch_loaders = opts[:batch_loaders]
26
27
  end
27
28
 
28
29
  # Serializes object(s)
@@ -47,8 +48,6 @@ class Serega
47
48
  def serialize_object(object)
48
49
  plan.points.each_with_object({}) do |point, container|
49
50
  serialize_point(object, point, container)
50
- rescue SeregaError
51
- raise
52
51
  rescue => error
53
52
  reraise_with_serialized_attribute_details(error, point)
54
53
  end
@@ -57,20 +56,23 @@ class Serega
57
56
  # Patched in:
58
57
  # - plugin :if (conditionally skips serializing this point)
59
58
  def serialize_point(object, point, container)
60
- attach_value(object, point, container)
59
+ if point.batch?
60
+ attacher = lambda { |obj, batches| attach_value(obj, point, container, batches: batches) }
61
+ batch_loaders.remember(point, object, attacher)
62
+ container[point.name] = nil # Reserve attribute place in resulted hash. We will set correct value later
63
+ else
64
+ attach_value(object, point, container, batches: nil)
65
+ end
61
66
  end
62
67
 
63
- # Patched in:
64
- # - plugin :batch (remembers key for batch loading values instead of attaching)
65
- def attach_value(object, point, container)
66
- value = point.value(object, context)
68
+ def attach_value(object, point, container, batches: nil)
69
+ value = point.value(object, context, batches: batches)
67
70
  final_value = final_value(value, point)
68
71
  attach_final_value(final_value, point, container)
69
72
  end
70
73
 
71
74
  # Patched in:
72
75
  # - plugin :if (conditionally skips attaching)
73
- # - plugin :batch :if extension (removes prepared key)
74
76
  def attach_final_value(final_value, point, container)
75
77
  container[point.name] = final_value
76
78
  end
data/lib/serega/plan.rb CHANGED
@@ -65,6 +65,10 @@ class Serega
65
65
  # @return [Array<SeregaPlanPoint>] points to serialize
66
66
  attr_reader :points
67
67
 
68
+ # Shows if plan includes batch points
69
+ # @return [Array<SeregaPlanPoint>] points to serialize
70
+ attr_reader :has_batch_points
71
+
68
72
  #
69
73
  # Instantiate new serialization plan
70
74
  #
@@ -80,6 +84,7 @@ class Serega
80
84
  # @return [SeregaPlan] Serialization plan
81
85
  #
82
86
  def initialize(parent_plan_point, modifiers)
87
+ @has_batch_points = false # should be before assigning points, generated points can change this attribute
83
88
  @parent_plan_point = parent_plan_point
84
89
  @points = attributes_points(modifiers)
85
90
  end
@@ -91,6 +96,17 @@ class Serega
91
96
  self.class.serializer_class
92
97
  end
93
98
 
99
+ #
100
+ # Marks current plan and top-level plans as `has_batch_points`
101
+ # It is needed to initialize Batch Attribute Loaders when serialization starts
102
+ #
103
+ def mark_as_has_batch_points
104
+ return if has_batch_points
105
+
106
+ @has_batch_points = true
107
+ parent_plan_point.plan.mark_as_has_batch_points if parent_plan_point # rubocop:disable Style/SafeNavigation
108
+ end
109
+
94
110
  private
95
111
 
96
112
  def attributes_points(modifiers)