serega 0.35.0 → 0.37.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.
- checksums.yaml +4 -4
- data/README.md +74 -79
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +0 -5
- data/lib/serega/attribute_normalizer.rb +28 -70
- data/lib/serega/batch/attribute_loader.rb +35 -20
- data/lib/serega/batch/attribute_loaders.rb +65 -16
- data/lib/serega/batch/level.rb +48 -0
- data/lib/serega/config.rb +8 -10
- data/lib/serega/data_builder.rb +52 -0
- data/lib/serega/object_serializer.rb +7 -1
- data/lib/serega/plan.rb +33 -10
- data/lib/serega/plan_point.rb +0 -19
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +9 -54
- data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +1 -1
- data/lib/serega/plugins/if/if.rb +23 -0
- data/lib/serega/plugins/if/validations/check_opt_if.rb +7 -4
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +7 -4
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +7 -4
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +7 -4
- data/lib/serega/plugins/root/root.rb +80 -0
- data/lib/serega/validations/attribute/check_opt_preload.rb +11 -0
- data/lib/serega/validations/check_attribute_params.rb +0 -1
- data/lib/serega.rb +108 -28
- metadata +5 -8
- data/lib/serega/plugins/activerecord_preloads/lib/active_record_objects.rb +0 -31
- data/lib/serega/utils/format_user_preloads.rb +0 -58
- data/lib/serega/utils/preload_paths.rb +0 -53
- data/lib/serega/utils/preloads_constructor.rb +0 -77
- data/lib/serega/validations/attribute/check_opt_preload_path.rb +0 -89
data/lib/serega.rb
CHANGED
|
@@ -11,16 +11,17 @@ class Serega
|
|
|
11
11
|
# Frozen array
|
|
12
12
|
# @return [Array] frozen array
|
|
13
13
|
FROZEN_EMPTY_ARRAY = [].freeze
|
|
14
|
+
|
|
15
|
+
# Empty modifiers/serialization options (used when serializing with no opts provided)
|
|
16
|
+
FROZEN_EMPTY_OPTS = [FROZEN_EMPTY_HASH, nil].freeze
|
|
17
|
+
private_constant :FROZEN_EMPTY_OPTS
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
require_relative "serega/errors"
|
|
17
21
|
require_relative "serega/helpers/serializer_class_helper"
|
|
18
22
|
require_relative "serega/utils/enum_deep_dup"
|
|
19
23
|
require_relative "serega/utils/enum_deep_freeze"
|
|
20
|
-
require_relative "serega/utils/format_user_preloads"
|
|
21
24
|
require_relative "serega/utils/method_signature"
|
|
22
|
-
require_relative "serega/utils/preload_paths"
|
|
23
|
-
require_relative "serega/utils/preloads_constructor"
|
|
24
25
|
require_relative "serega/utils/symbol_name"
|
|
25
26
|
require_relative "serega/utils/to_hash"
|
|
26
27
|
require_relative "serega/attribute_value_resolvers/batch"
|
|
@@ -31,6 +32,7 @@ require_relative "serega/attribute"
|
|
|
31
32
|
require_relative "serega/attribute_normalizer"
|
|
32
33
|
require_relative "serega/batch/attribute_loader"
|
|
33
34
|
require_relative "serega/batch/attribute_loaders"
|
|
35
|
+
require_relative "serega/batch/level"
|
|
34
36
|
require_relative "serega/batch/loader"
|
|
35
37
|
require_relative "serega/validations/utils/check_allowed_keys"
|
|
36
38
|
require_relative "serega/validations/utils/check_opt_is_bool"
|
|
@@ -45,7 +47,6 @@ require_relative "serega/validations/attribute/check_opt_batch"
|
|
|
45
47
|
require_relative "serega/validations/attribute/check_opt_many"
|
|
46
48
|
require_relative "serega/validations/attribute/check_opt_method"
|
|
47
49
|
require_relative "serega/validations/attribute/check_opt_preload"
|
|
48
|
-
require_relative "serega/validations/attribute/check_opt_preload_path"
|
|
49
50
|
require_relative "serega/validations/attribute/check_opt_serializer"
|
|
50
51
|
require_relative "serega/validations/attribute/check_opt_value"
|
|
51
52
|
require_relative "serega/validations/initiate/check_modifiers"
|
|
@@ -58,6 +59,7 @@ require_relative "serega/config"
|
|
|
58
59
|
require_relative "serega/object_serializer"
|
|
59
60
|
require_relative "serega/plan_point"
|
|
60
61
|
require_relative "serega/plan"
|
|
62
|
+
require_relative "serega/data_builder"
|
|
61
63
|
require_relative "serega/plugins"
|
|
62
64
|
|
|
63
65
|
class Serega
|
|
@@ -215,6 +217,38 @@ class Serega
|
|
|
215
217
|
batch_loaders[batch_loader.name] = batch_loader
|
|
216
218
|
end
|
|
217
219
|
|
|
220
|
+
#
|
|
221
|
+
# Registers (or returns) the handler used to preload an attribute's
|
|
222
|
+
# associations onto the records gathered during serialization.
|
|
223
|
+
#
|
|
224
|
+
# The handler is called once per preloaded attribute with the gathered
|
|
225
|
+
# objects and that attribute's preloads. ORM plugins register a handler
|
|
226
|
+
# that performs the actual eager loading.
|
|
227
|
+
#
|
|
228
|
+
# @example with a block
|
|
229
|
+
# preload_with { |objects, preloads| MyORM.eager_load(objects, preloads) }
|
|
230
|
+
#
|
|
231
|
+
# @example with a callable value
|
|
232
|
+
# preload_with MyPreloader
|
|
233
|
+
#
|
|
234
|
+
# @param value [#call, nil] Preload handler accepting two positional arguments
|
|
235
|
+
# @param block [Proc] Preload handler accepting two positional arguments
|
|
236
|
+
#
|
|
237
|
+
# @return [#call, nil] The registered preload handler
|
|
238
|
+
#
|
|
239
|
+
def preload_with(value = nil, &block)
|
|
240
|
+
return @preload_with if value.nil? && block.nil?
|
|
241
|
+
raise SeregaError, "preload_with accepts a single callable or a block, not both" if value && block
|
|
242
|
+
|
|
243
|
+
handler = value || block
|
|
244
|
+
raise SeregaError, "preload_with value must be a Proc or respond to #call" if !handler.is_a?(Proc) && !handler.respond_to?(:call)
|
|
245
|
+
|
|
246
|
+
signature = SeregaUtils::MethodSignature.call(handler, pos_limit: 2)
|
|
247
|
+
raise SeregaError, "preload_with handler must accept two positional arguments: (objects, preloads)" unless signature == "2"
|
|
248
|
+
|
|
249
|
+
@preload_with = handler
|
|
250
|
+
end
|
|
251
|
+
|
|
218
252
|
#
|
|
219
253
|
# Serializes provided object to Hash
|
|
220
254
|
#
|
|
@@ -230,25 +264,45 @@ class Serega
|
|
|
230
264
|
# @return [Hash] Serialization result
|
|
231
265
|
#
|
|
232
266
|
def call(object, opts = nil)
|
|
233
|
-
opts
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if opts.empty?
|
|
237
|
-
modifiers_opts = FROZEN_EMPTY_HASH
|
|
238
|
-
serialize_opts = nil
|
|
239
|
-
else
|
|
240
|
-
opts.transform_keys!(&:to_sym)
|
|
241
|
-
serialize_opts = opts.except(*initiate_keys)
|
|
242
|
-
modifiers_opts = opts.slice(*initiate_keys)
|
|
243
|
-
end
|
|
244
|
-
|
|
267
|
+
opts = opts&.transform_keys(&:to_sym)
|
|
268
|
+
modifiers_opts = init_modifier_opts(opts)
|
|
269
|
+
serialize_opts = init_serialize_opts(opts)
|
|
245
270
|
new(modifiers_opts).to_h(object, serialize_opts)
|
|
246
271
|
end
|
|
247
272
|
|
|
273
|
+
#
|
|
274
|
+
# Serializes provided object to a tree of Ruby Data objects
|
|
275
|
+
#
|
|
276
|
+
# @param object [Object] Serialized object
|
|
277
|
+
# @param opts [Hash, nil] Serializer modifiers and other instantiating options
|
|
278
|
+
# @option opts [Array, Hash, String, Symbol] :only The only attributes to serialize
|
|
279
|
+
# @option opts [Array, Hash, String, Symbol] :except Attributes to hide
|
|
280
|
+
# @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
|
|
281
|
+
# @option opts [Boolean] :validate Validates provided modifiers (Default is true)
|
|
282
|
+
# @option opts [Hash] :context Serialization context
|
|
283
|
+
# @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
|
|
284
|
+
#
|
|
285
|
+
# @return [Data, Array<Data>, nil] Serialization result as Data object(s)
|
|
286
|
+
#
|
|
287
|
+
def to_data(object, opts = nil)
|
|
288
|
+
opts = opts&.transform_keys(&:to_sym)
|
|
289
|
+
modifiers_opts = init_modifier_opts(opts)
|
|
290
|
+
serialize_opts = init_serialize_opts(opts)
|
|
291
|
+
new(modifiers_opts).to_data(object, serialize_opts)
|
|
292
|
+
end
|
|
293
|
+
|
|
248
294
|
alias_method :to_h, :call
|
|
249
295
|
|
|
250
296
|
private
|
|
251
297
|
|
|
298
|
+
def init_modifier_opts(opts)
|
|
299
|
+
(!opts || opts.empty?) ? FROZEN_EMPTY_HASH : opts.slice(*config.initiate_keys)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def init_serialize_opts(opts)
|
|
303
|
+
(!opts || opts.empty?) ? nil : opts.except(*config.initiate_keys)
|
|
304
|
+
end
|
|
305
|
+
|
|
252
306
|
# Patched in:
|
|
253
307
|
# - plugin :metadata (defines MetaAttribute and copies meta_attributes to subclasses)
|
|
254
308
|
# - plugin :presenter (defines Presenter)
|
|
@@ -266,6 +320,10 @@ class Serega
|
|
|
266
320
|
attribute_normalizer_class.serializer_class = subclass
|
|
267
321
|
subclass.const_set(:SeregaAttributeNormalizer, attribute_normalizer_class)
|
|
268
322
|
|
|
323
|
+
data_builder_class = Class.new(self::SeregaDataBuilder)
|
|
324
|
+
data_builder_class.serializer_class = subclass
|
|
325
|
+
subclass.const_set(:SeregaDataBuilder, data_builder_class)
|
|
326
|
+
|
|
269
327
|
plan_class = Class.new(self::SeregaPlan)
|
|
270
328
|
plan_class.serializer_class = subclass
|
|
271
329
|
subclass.const_set(:SeregaPlan, plan_class)
|
|
@@ -313,6 +371,9 @@ class Serega
|
|
|
313
371
|
subclass.batch(loader.name, loader.block)
|
|
314
372
|
end
|
|
315
373
|
|
|
374
|
+
# Assign same preload handler
|
|
375
|
+
subclass.preload_with(preload_with) if preload_with
|
|
376
|
+
|
|
316
377
|
super
|
|
317
378
|
end
|
|
318
379
|
end
|
|
@@ -355,17 +416,14 @@ class Serega
|
|
|
355
416
|
# Serializes provided object to Hash
|
|
356
417
|
#
|
|
357
418
|
# @param object [Object] Serialized object
|
|
358
|
-
# @param opts [Hash, nil]
|
|
419
|
+
# @param opts [Hash, nil] Serializing options
|
|
359
420
|
# @option opts [Hash] :context Serialization context
|
|
360
421
|
# @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
|
|
361
422
|
#
|
|
362
423
|
# @return [Hash] Serialization result
|
|
363
424
|
#
|
|
364
425
|
def call(object, opts = nil)
|
|
365
|
-
opts =
|
|
366
|
-
self.class::CheckSerializeParams.new(opts).validate unless opts.empty?
|
|
367
|
-
|
|
368
|
-
opts[:context] ||= {}
|
|
426
|
+
opts = prepare_initial_serialization_opts(object, opts)
|
|
369
427
|
serialize(object, opts)
|
|
370
428
|
end
|
|
371
429
|
|
|
@@ -374,9 +432,22 @@ class Serega
|
|
|
374
432
|
call(object, opts)
|
|
375
433
|
end
|
|
376
434
|
|
|
377
|
-
#
|
|
378
|
-
|
|
379
|
-
|
|
435
|
+
#
|
|
436
|
+
# Serializes provided object to Data objects
|
|
437
|
+
# Patched in:
|
|
438
|
+
# - plugin :root (adds a data-object for a root level keys)
|
|
439
|
+
#
|
|
440
|
+
# @param object [Object] Serialized object
|
|
441
|
+
# @param opts [Hash, nil] Serializing options
|
|
442
|
+
# @option opts [Hash] :context Serialization context
|
|
443
|
+
# @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
|
|
444
|
+
#
|
|
445
|
+
# @return [Data] Serialization result
|
|
446
|
+
#
|
|
447
|
+
def to_data(object, opts = nil)
|
|
448
|
+
opts = prepare_initial_serialization_opts(object, opts)
|
|
449
|
+
serialized_data = serialize(object, opts)
|
|
450
|
+
self.class::SeregaDataBuilder.call(self, serialized_data)
|
|
380
451
|
end
|
|
381
452
|
|
|
382
453
|
private
|
|
@@ -404,15 +475,24 @@ class Serega
|
|
|
404
475
|
SeregaUtils::ToHash.call(value)
|
|
405
476
|
end
|
|
406
477
|
|
|
478
|
+
def prepare_initial_serialization_opts(object, opts)
|
|
479
|
+
opts = opts ? opts.transform_keys(&:to_sym) : {}
|
|
480
|
+
self.class::CheckSerializeParams.new(opts).validate unless opts.empty?
|
|
481
|
+
|
|
482
|
+
opts[:context] ||= {}
|
|
483
|
+
opts[:batch_loaders] = SeregaBatch::AttributeLoaders.new(opts[:context])
|
|
484
|
+
opts[:many] = object.is_a?(Enumerable) unless opts.key?(:many)
|
|
485
|
+
opts[:plan] = plan
|
|
486
|
+
opts
|
|
487
|
+
end
|
|
488
|
+
|
|
407
489
|
# Patched in:
|
|
408
|
-
# - plugin :activerecord_preloads (loads defined :preloads to object)
|
|
409
490
|
# - plugin :root (wraps result `{ root => result }`)
|
|
410
491
|
# - plugin :context_metadata (adds context metadata to final result)
|
|
411
492
|
# - plugin :metadata (adds metadata to final result)
|
|
412
493
|
def serialize(object, opts)
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
batch_loaders&.load_all(opts[:context])
|
|
494
|
+
result = self.class::SeregaObjectSerializer.new(**opts).serialize(object)
|
|
495
|
+
opts[:batch_loaders].load_all
|
|
416
496
|
result
|
|
417
497
|
end
|
|
418
498
|
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.
|
|
4
|
+
version: 0.37.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrey Glushkov
|
|
@@ -35,8 +35,10 @@ files:
|
|
|
35
35
|
- lib/serega/attribute_value_resolvers/keyword.rb
|
|
36
36
|
- lib/serega/batch/attribute_loader.rb
|
|
37
37
|
- lib/serega/batch/attribute_loaders.rb
|
|
38
|
+
- lib/serega/batch/level.rb
|
|
38
39
|
- lib/serega/batch/loader.rb
|
|
39
40
|
- lib/serega/config.rb
|
|
41
|
+
- lib/serega/data_builder.rb
|
|
40
42
|
- lib/serega/errors.rb
|
|
41
43
|
- lib/serega/helpers/serializer_class_helper.rb
|
|
42
44
|
- lib/serega/object_serializer.rb
|
|
@@ -44,7 +46,6 @@ files:
|
|
|
44
46
|
- lib/serega/plan_point.rb
|
|
45
47
|
- lib/serega/plugins.rb
|
|
46
48
|
- lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb
|
|
47
|
-
- lib/serega/plugins/activerecord_preloads/lib/active_record_objects.rb
|
|
48
49
|
- lib/serega/plugins/activerecord_preloads/lib/preloader.rb
|
|
49
50
|
- lib/serega/plugins/camel_case/camel_case.rb
|
|
50
51
|
- lib/serega/plugins/context_metadata/context_metadata.rb
|
|
@@ -72,10 +73,7 @@ files:
|
|
|
72
73
|
- lib/serega/plugins/string_modifiers/string_modifiers.rb
|
|
73
74
|
- lib/serega/utils/enum_deep_dup.rb
|
|
74
75
|
- lib/serega/utils/enum_deep_freeze.rb
|
|
75
|
-
- lib/serega/utils/format_user_preloads.rb
|
|
76
76
|
- lib/serega/utils/method_signature.rb
|
|
77
|
-
- lib/serega/utils/preload_paths.rb
|
|
78
|
-
- lib/serega/utils/preloads_constructor.rb
|
|
79
77
|
- lib/serega/utils/symbol_name.rb
|
|
80
78
|
- lib/serega/utils/to_hash.rb
|
|
81
79
|
- lib/serega/validations/attribute/check_block.rb
|
|
@@ -87,7 +85,6 @@ files:
|
|
|
87
85
|
- lib/serega/validations/attribute/check_opt_many.rb
|
|
88
86
|
- lib/serega/validations/attribute/check_opt_method.rb
|
|
89
87
|
- lib/serega/validations/attribute/check_opt_preload.rb
|
|
90
|
-
- lib/serega/validations/attribute/check_opt_preload_path.rb
|
|
91
88
|
- lib/serega/validations/attribute/check_opt_serializer.rb
|
|
92
89
|
- lib/serega/validations/attribute/check_opt_value.rb
|
|
93
90
|
- lib/serega/validations/check_attribute_params.rb
|
|
@@ -114,14 +111,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
114
111
|
requirements:
|
|
115
112
|
- - ">="
|
|
116
113
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 2.
|
|
114
|
+
version: 3.2.0
|
|
118
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
116
|
requirements:
|
|
120
117
|
- - ">="
|
|
121
118
|
- !ruby/object:Gem::Version
|
|
122
119
|
version: '0'
|
|
123
120
|
requirements: []
|
|
124
|
-
rubygems_version: 4.0.
|
|
121
|
+
rubygems_version: 4.0.13
|
|
125
122
|
specification_version: 4
|
|
126
123
|
summary: JSON Serializer
|
|
127
124
|
test_files: []
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaPlugins
|
|
5
|
-
module ActiverecordPreloads
|
|
6
|
-
# Detects and returns found ActiveRecord objects
|
|
7
|
-
class ActiveRecordObjects
|
|
8
|
-
class << self
|
|
9
|
-
# Iterates over provided data and selects ActiveRecord objects
|
|
10
|
-
# @param data [Hash,Array,Object] any data
|
|
11
|
-
# @return [Array] Found ActiveRecord objects
|
|
12
|
-
def call(data)
|
|
13
|
-
res = []
|
|
14
|
-
extract(data, res)
|
|
15
|
-
res
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
def extract(data, res)
|
|
21
|
-
case data
|
|
22
|
-
when ActiveRecord::Base then res << data
|
|
23
|
-
when Array then data.each { |value| extract(value, res) }
|
|
24
|
-
when Hash then data.each_value { |value| extract(value, res) }
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaUtils
|
|
5
|
-
#
|
|
6
|
-
# Utility that helps to transform user provided preloads to hash
|
|
7
|
-
#
|
|
8
|
-
class FormatUserPreloads
|
|
9
|
-
class << self
|
|
10
|
-
#
|
|
11
|
-
# Transforms user provided preloads to hash
|
|
12
|
-
#
|
|
13
|
-
# @param value [Array,Hash,String,Symbol,nil,false] preloads
|
|
14
|
-
#
|
|
15
|
-
# @return [Hash] preloads transformed to hash
|
|
16
|
-
#
|
|
17
|
-
def call(value)
|
|
18
|
-
case value
|
|
19
|
-
when Array then array_to_hash(value)
|
|
20
|
-
when FalseClass then nil_to_hash(value)
|
|
21
|
-
when Hash then hash_to_hash(value)
|
|
22
|
-
when NilClass then nil_to_hash(value)
|
|
23
|
-
when String then string_to_hash(value)
|
|
24
|
-
when Symbol then symbol_to_hash(value)
|
|
25
|
-
else raise Serega::SeregaError,
|
|
26
|
-
"Preload option value can consist from Symbols, Arrays, Hashes (#{value.class} #{value.inspect} was provided)"
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def array_to_hash(values)
|
|
33
|
-
values.each_with_object({}) do |value, obj|
|
|
34
|
-
obj.merge!(call(value))
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def hash_to_hash(values)
|
|
39
|
-
values.each_with_object({}) do |(key, value), obj|
|
|
40
|
-
obj[key.to_sym] = call(value)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def nil_to_hash(_value)
|
|
45
|
-
{}
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def string_to_hash(value)
|
|
49
|
-
{value.to_sym => {}}
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def symbol_to_hash(value)
|
|
53
|
-
{value => {}}
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaUtils
|
|
5
|
-
#
|
|
6
|
-
# Utility that helps to transform preloads to array of paths
|
|
7
|
-
# It is used to validate manually set `:preload_path` attribute option has one of allowed values.
|
|
8
|
-
# `:preload_path` option can be used to specify where nested preloads must be attached.
|
|
9
|
-
#
|
|
10
|
-
# Example:
|
|
11
|
-
#
|
|
12
|
-
# call({ a: { b: { c: {}, d: {} } }, e: {} })
|
|
13
|
-
#
|
|
14
|
-
# => [
|
|
15
|
-
# [:a],
|
|
16
|
-
# [:a, :b],
|
|
17
|
-
# [:a, :b, :c],
|
|
18
|
-
# [:a, :b, :d],
|
|
19
|
-
# [:e]
|
|
20
|
-
# ]
|
|
21
|
-
class PreloadPaths
|
|
22
|
-
class << self
|
|
23
|
-
#
|
|
24
|
-
# Transforms user provided preloads to array of paths
|
|
25
|
-
#
|
|
26
|
-
# @param preloads [Array,Hash,String,Symbol,nil,false] association(s) to preload
|
|
27
|
-
#
|
|
28
|
-
# @return [Array] transformed preloads
|
|
29
|
-
#
|
|
30
|
-
def call(preloads)
|
|
31
|
-
formatted_preloads = FormatUserPreloads.call(preloads)
|
|
32
|
-
return FROZEN_EMPTY_ARRAY if formatted_preloads.empty?
|
|
33
|
-
|
|
34
|
-
paths(formatted_preloads, [], [])
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def paths(formatted_preloads, path, result)
|
|
40
|
-
formatted_preloads.each do |key, nested_preloads|
|
|
41
|
-
path << key
|
|
42
|
-
result << path.dup
|
|
43
|
-
|
|
44
|
-
paths(nested_preloads, path, result)
|
|
45
|
-
path.pop
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
result
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaUtils
|
|
5
|
-
#
|
|
6
|
-
# Finds preloads for provided attributes plan
|
|
7
|
-
#
|
|
8
|
-
class PreloadsConstructor
|
|
9
|
-
class << self
|
|
10
|
-
#
|
|
11
|
-
# Constructs preloads hash for given attributes plan
|
|
12
|
-
#
|
|
13
|
-
# @param plan [Array<SeregaPlanPoint>] Serialization plan
|
|
14
|
-
#
|
|
15
|
-
# @return [Hash]
|
|
16
|
-
#
|
|
17
|
-
def call(plan)
|
|
18
|
-
return FROZEN_EMPTY_HASH unless plan
|
|
19
|
-
|
|
20
|
-
preloads = {}
|
|
21
|
-
append_many(preloads, plan)
|
|
22
|
-
preloads
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def append_many(preloads, plan)
|
|
28
|
-
plan.points.each do |point|
|
|
29
|
-
current_preloads = point.attribute.preloads
|
|
30
|
-
next unless current_preloads
|
|
31
|
-
|
|
32
|
-
child_plan = point.child_plan
|
|
33
|
-
current_preloads = SeregaUtils::EnumDeepDup.call(current_preloads) if child_plan
|
|
34
|
-
append_current(preloads, current_preloads)
|
|
35
|
-
next unless child_plan
|
|
36
|
-
|
|
37
|
-
each_child_preloads(preloads, point.preloads_path) do |child_preloads|
|
|
38
|
-
append_many(child_preloads, child_plan)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def append_current(preloads, current_preloads)
|
|
44
|
-
merge(preloads, current_preloads) unless current_preloads.empty?
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def merge(preloads, current_preloads)
|
|
48
|
-
preloads.merge!(current_preloads) do |_key, value_one, value_two|
|
|
49
|
-
merge(value_one, value_two)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def each_child_preloads(preloads, preloads_path)
|
|
54
|
-
return yield(preloads) if preloads_path.nil?
|
|
55
|
-
|
|
56
|
-
if preloads_path[0].is_a?(Array)
|
|
57
|
-
preloads_path.each do |path|
|
|
58
|
-
yield dig_fetch(preloads, path)
|
|
59
|
-
end
|
|
60
|
-
else
|
|
61
|
-
yield dig_fetch(preloads, preloads_path)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def dig_fetch(preloads, preloads_path)
|
|
66
|
-
return preloads if !preloads_path || preloads_path.empty?
|
|
67
|
-
|
|
68
|
-
preloads_path.each do |path|
|
|
69
|
-
preloads = preloads.fetch(path)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
preloads
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaValidations
|
|
5
|
-
module Attribute
|
|
6
|
-
#
|
|
7
|
-
# Validator for attribute :preload_path option
|
|
8
|
-
#
|
|
9
|
-
class CheckOptPreloadPath
|
|
10
|
-
class << self
|
|
11
|
-
#
|
|
12
|
-
# Checks preload_path option
|
|
13
|
-
#
|
|
14
|
-
# @param opts [Hash] Attribute options
|
|
15
|
-
#
|
|
16
|
-
# @raise [SeregaError] validation error
|
|
17
|
-
#
|
|
18
|
-
# @return [void]
|
|
19
|
-
#
|
|
20
|
-
def call(opts)
|
|
21
|
-
return if exactly_nil?(opts, :preload_path) # allow to provide nil anyway
|
|
22
|
-
|
|
23
|
-
path = opts[:preload_path]
|
|
24
|
-
check_usage_with_other_options(path, opts)
|
|
25
|
-
return unless opts[:serializer]
|
|
26
|
-
|
|
27
|
-
check_allowed(path, opts)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def exactly_nil?(opts, opt_name)
|
|
33
|
-
opts.fetch(opt_name, false).nil?
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def check_allowed(path, opts)
|
|
37
|
-
allowed_paths = SeregaUtils::PreloadPaths.call(opts[:preload])
|
|
38
|
-
check_required_when_many_allowed(path, allowed_paths)
|
|
39
|
-
check_in_allowed(path, allowed_paths)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def check_usage_with_other_options(path, opts)
|
|
43
|
-
return unless path
|
|
44
|
-
|
|
45
|
-
preload = opts[:preload]
|
|
46
|
-
raise SeregaError, "Invalid option preload_path: #{path.inspect}. Can be provided only when :preload option provided" unless preload
|
|
47
|
-
|
|
48
|
-
serializer = opts[:serializer]
|
|
49
|
-
raise SeregaError, "Invalid option preload_path: #{path.inspect}. Can be provided only when :serializer option provided" unless serializer
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def check_required_when_many_allowed(path, allowed)
|
|
53
|
-
return if path || (allowed.size < 2)
|
|
54
|
-
|
|
55
|
-
raise SeregaError, "Option :preload_path must be provided. Possible values: #{allowed.inspect[1..-2]}"
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def check_in_allowed(path, allowed)
|
|
59
|
-
return if !path && allowed.size <= 1
|
|
60
|
-
|
|
61
|
-
if multiple_preload_paths_provided?(path)
|
|
62
|
-
check_many(path, allowed)
|
|
63
|
-
else
|
|
64
|
-
check_one(path, allowed)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def check_one(path, allowed)
|
|
69
|
-
formatted_path = Array(path).map(&:to_sym)
|
|
70
|
-
return if allowed.include?(formatted_path)
|
|
71
|
-
|
|
72
|
-
raise SeregaError,
|
|
73
|
-
"Invalid preload_path (#{path.inspect}). " \
|
|
74
|
-
"Can be one of #{allowed.inspect[1..-2]}"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def check_many(paths, allowed)
|
|
78
|
-
paths.each { |path| check_one(path, allowed) }
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Check value is Array in Array
|
|
82
|
-
def multiple_preload_paths_provided?(value)
|
|
83
|
-
value.is_a?(Array) && value[0].is_a?(Array)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|