serega 0.11.2 → 0.12.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 +62 -13
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +9 -4
- data/lib/serega/attribute_normalizer.rb +4 -13
- data/lib/serega/object_serializer.rb +11 -0
- data/lib/serega/plan.rb +20 -25
- data/lib/serega/plan_point.rb +13 -16
- data/lib/serega/plugins/batch/lib/loader.rb +25 -7
- data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +1 -9
- data/lib/serega/plugins/metadata/metadata.rb +5 -0
- data/lib/serega/plugins/preloads/lib/modules/attribute.rb +28 -0
- data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +99 -0
- data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +22 -0
- data/lib/serega/plugins/preloads/lib/modules/config.rb +19 -0
- data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +41 -0
- data/lib/serega/plugins/preloads/lib/preload_paths.rb +46 -0
- data/lib/serega/plugins/preloads/lib/preloads_config.rb +62 -0
- data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +20 -7
- data/lib/serega/plugins/preloads/preloads.rb +12 -210
- data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +54 -15
- metadata +9 -3
- data/lib/serega/plugins/preloads/lib/main_preload_path.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52535c7fab3897f65aec4e156ab5681e8595b4519df1e79a777688cee486a480
|
4
|
+
data.tar.gz: 16adbd9f036b0e23928049df873966f264db1004d2c4c1bc9e28422d687efd12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93a6aaefdf954556e31bd604ead1e376d5ffbb74225251e635bf5133af53da26f47e924f1f88349fa4656e228ddca573347d9f8fa15cc30bc6052f7e5dfec3bd
|
7
|
+
data.tar.gz: c84135d04c79aae1ccd03a3f3ac7dbed53dbf75165575afbb61ad6e32a558318cac2a8311c31b76df90fb74823facdf4677821d8f7ae8284f04782d33b04ad41
|
data/README.md
CHANGED
@@ -415,30 +415,79 @@ UserSerializer.new(
|
|
415
415
|
```
|
416
416
|
|
417
417
|
---
|
418
|
-
One tricky case, that you will probably never see in real life:
|
419
418
|
|
420
|
-
|
419
|
+
#### SPECIFIC CASE #1: Serializing same object as association
|
420
|
+
|
421
|
+
For example you decided to show your current user as "user" and "user_stats".
|
422
|
+
Where stats rely on user fields and some other associations.
|
423
|
+
You should specify `preload: nil` to preload nested associations, if any, to "user".
|
421
424
|
|
422
425
|
```ruby
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
426
|
+
class AppSerializer < Serega
|
427
|
+
plugin :preloads,
|
428
|
+
auto_preload_attributes_with_delegate: true,
|
429
|
+
auto_preload_attributes_with_serializer: true,
|
430
|
+
auto_hide_attributes_with_preload: true
|
431
|
+
end
|
432
|
+
|
433
|
+
class UserSerializer < AppSerializer
|
434
|
+
attribute :username
|
435
|
+
attribute :user_stats,
|
436
|
+
serializer: 'UserStatSerializer'
|
437
|
+
value: proc { |user| user },
|
438
|
+
preload: nil
|
439
|
+
end
|
427
440
|
```
|
428
441
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
442
|
+
#### SPECIFIC CASE #2: Serializing multiple associations as single relation
|
443
|
+
|
444
|
+
For example "user" has two relations - "new_profile", "old_profile", and also
|
445
|
+
profiles have "avatar" association. And you decided to serialize profiles in one
|
446
|
+
array. You can specify `preload_path: [[:new_profile], [:old_profile]]` to
|
447
|
+
achieve this:
|
448
|
+
|
449
|
+
```ruby
|
450
|
+
class AppSerializer < Serega
|
451
|
+
plugin :preloads,
|
452
|
+
auto_preload_attributes_with_delegate: true,
|
453
|
+
auto_preload_attributes_with_serializer: true
|
454
|
+
end
|
455
|
+
|
456
|
+
class UserSerializer < AppSerializer
|
457
|
+
attribute :username
|
458
|
+
attribute :profiles,
|
459
|
+
serializer: 'ProfileSerializer',
|
460
|
+
value: proc { |user| [user.new_profile, user.old_profile] },
|
461
|
+
preload: [:new_profile, :old_profile],
|
462
|
+
preload_path: [[:new_profile], [:old_profile]] # <--- like here
|
463
|
+
end
|
464
|
+
|
465
|
+
class ProfileSerializer < AppSerializer
|
466
|
+
attribute :avatar, serializer: 'AvatarSerializer'
|
467
|
+
end
|
468
|
+
|
469
|
+
class AvatarSerializer < AppSerializer
|
470
|
+
end
|
471
|
+
|
472
|
+
UserSerializer.new.preloads
|
473
|
+
# => {:new_profile=>{:avatar=>{}}, :old_profile=>{:avatar=>{}}}
|
474
|
+
```
|
475
|
+
|
476
|
+
#### SPECIFIC CASE #3: Preload association through another association
|
433
477
|
|
434
478
|
```ruby
|
435
479
|
attribute :image,
|
480
|
+
preload: { attachment: :blob }, # <--------- like this one
|
481
|
+
value: proc { |record| record.attachment },
|
436
482
|
serializer: ImageSerializer,
|
437
|
-
|
438
|
-
preload_path: %i[attachment],
|
439
|
-
value: proc { |record| record.attachment }
|
483
|
+
preload_path: [:attachment] # or preload_path: [:attachment, :blob]
|
440
484
|
```
|
441
485
|
|
486
|
+
In this case we don't know if preloads defined in ImageSerializer, should be
|
487
|
+
preloaded to `attachment` or `blob`, so please specify `preload_path` manually.
|
488
|
+
You can specify `preload_path: nil` if you are sure that there are no preloads
|
489
|
+
inside ImageSerializer.
|
490
|
+
|
442
491
|
---
|
443
492
|
|
444
493
|
📌 Plugin `:preloads` only allows to group preloads together in single Hash, but
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.12.0
|
data/lib/serega/attribute.rb
CHANGED
@@ -82,13 +82,18 @@ class Serega
|
|
82
82
|
#
|
83
83
|
# Checks if attribute must be added to serialized response
|
84
84
|
#
|
85
|
-
# @param
|
86
|
-
# @
|
87
|
-
# @
|
85
|
+
# @param modifiers [Hash] Serialization modifiers
|
86
|
+
# @option modifiers [Hash] :only The only attributes to serialize
|
87
|
+
# @option modifiers [Hash] :except Attributes to hide
|
88
|
+
# @option modifiers [Hash] :with Hidden attributes to serialize additionally
|
88
89
|
#
|
89
90
|
# @return [Boolean]
|
90
91
|
#
|
91
|
-
def visible?(
|
92
|
+
def visible?(modifiers)
|
93
|
+
except = modifiers[:except] || FROZEN_EMPTY_HASH
|
94
|
+
only = modifiers[:only] || FROZEN_EMPTY_HASH
|
95
|
+
with = modifiers[:with] || FROZEN_EMPTY_HASH
|
96
|
+
|
92
97
|
return false if except.member?(name) && except[name].empty?
|
93
98
|
return true if only.member?(name)
|
94
99
|
return true if with.member?(name)
|
@@ -107,6 +107,7 @@ class Serega
|
|
107
107
|
#
|
108
108
|
# Patched in:
|
109
109
|
# - plugin :preloads (returns true by default if config option auto_hide_attribute_with_preloads is enabled)
|
110
|
+
# - plugin :batch (returns true by default if auto_hide option was set and attribute has batch loader)
|
110
111
|
#
|
111
112
|
def prepare_hide
|
112
113
|
init_opts[:hide]
|
@@ -135,7 +136,7 @@ class Serega
|
|
135
136
|
def prepare_keyword_block
|
136
137
|
key_method_name = key
|
137
138
|
proc do |object|
|
138
|
-
|
139
|
+
object.public_send(key_method_name)
|
139
140
|
end
|
140
141
|
end
|
141
142
|
|
@@ -150,24 +151,14 @@ class Serega
|
|
150
151
|
|
151
152
|
if allow_nil
|
152
153
|
proc do |object|
|
153
|
-
|
154
|
-
object.public_send(delegate_to)&.public_send(key_method_name)
|
155
|
-
end
|
154
|
+
object.public_send(delegate_to)&.public_send(key_method_name)
|
156
155
|
end
|
157
156
|
else
|
158
157
|
proc do |object|
|
159
|
-
|
160
|
-
object.public_send(delegate_to).public_send(key_method_name)
|
161
|
-
end
|
158
|
+
object.public_send(delegate_to).public_send(key_method_name)
|
162
159
|
end
|
163
160
|
end
|
164
161
|
end
|
165
|
-
|
166
|
-
def handle_no_method_error
|
167
|
-
yield
|
168
|
-
rescue NoMethodError => error
|
169
|
-
raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
|
170
|
-
end
|
171
162
|
end
|
172
163
|
|
173
164
|
extend Serega::SeregaHelpers::SerializerClassHelper
|
@@ -47,6 +47,10 @@ class Serega
|
|
47
47
|
def serialize_object(object)
|
48
48
|
plan.points.each_with_object({}) do |point, container|
|
49
49
|
serialize_point(object, point, container)
|
50
|
+
rescue SeregaError
|
51
|
+
raise
|
52
|
+
rescue => error
|
53
|
+
reraise_with_serialized_attribute_details(error, point)
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
@@ -85,6 +89,13 @@ class Serega
|
|
85
89
|
def array?(object, many)
|
86
90
|
many.nil? ? object.is_a?(Enumerable) : many
|
87
91
|
end
|
92
|
+
|
93
|
+
def reraise_with_serialized_attribute_details(error, point)
|
94
|
+
raise error.exception(<<~MESSAGE.strip)
|
95
|
+
#{error.message}
|
96
|
+
(when serializing '#{point.name}' attribute in #{self.class.serializer_class})
|
97
|
+
MESSAGE
|
98
|
+
end
|
88
99
|
end
|
89
100
|
|
90
101
|
extend Serega::SeregaHelpers::SerializerClassHelper
|
data/lib/serega/plan.rb
CHANGED
@@ -22,34 +22,22 @@ class Serega
|
|
22
22
|
#
|
23
23
|
def call(opts)
|
24
24
|
max_cache_size = serializer_class.config.max_cached_plans_per_serializer_count
|
25
|
-
return
|
25
|
+
return new(opts) if max_cache_size.zero?
|
26
26
|
|
27
27
|
cached_plan_for(opts, max_cache_size)
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
def plan_for(opts)
|
33
|
-
new(**modifiers(opts))
|
34
|
-
end
|
35
|
-
|
36
32
|
def cached_plan_for(opts, max_cache_size)
|
37
33
|
@cache ||= {}
|
38
34
|
cache_key = construct_cache_key(opts)
|
39
35
|
|
40
|
-
plan = @cache[cache_key] ||=
|
36
|
+
plan = @cache[cache_key] ||= new(opts)
|
41
37
|
@cache.shift if @cache.length > max_cache_size
|
42
38
|
plan
|
43
39
|
end
|
44
40
|
|
45
|
-
def modifiers(opts)
|
46
|
-
{
|
47
|
-
only: opts[:only] || FROZEN_EMPTY_HASH,
|
48
|
-
except: opts[:except] || FROZEN_EMPTY_HASH,
|
49
|
-
with: opts[:with] || FROZEN_EMPTY_HASH
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
41
|
def construct_cache_key(opts, cache_key = nil)
|
54
42
|
return nil if opts.empty?
|
55
43
|
|
@@ -69,10 +57,14 @@ class Serega
|
|
69
57
|
# SeregaPlan instance methods
|
70
58
|
#
|
71
59
|
module InstanceMethods
|
72
|
-
# Parent plan point
|
60
|
+
# Parent plan point
|
73
61
|
# @return [SeregaPlanPoint, nil]
|
74
62
|
attr_reader :parent_plan_point
|
75
63
|
|
64
|
+
# Sets new parent plan point
|
65
|
+
# @return [SeregaPlanPoint] new parent plan point
|
66
|
+
attr_writer :parent_plan_point
|
67
|
+
|
76
68
|
# Serialization points
|
77
69
|
# @return [Array<SeregaPlanPoint>] points to serialize
|
78
70
|
attr_reader :points
|
@@ -80,17 +72,15 @@ class Serega
|
|
80
72
|
#
|
81
73
|
# Instantiate new serialization plan.
|
82
74
|
#
|
83
|
-
# @param
|
84
|
-
# @option
|
85
|
-
# @option
|
86
|
-
# @option
|
87
|
-
# @option opts [Hash] :with Attributes (usually marked hide: true`) to serialize additionally
|
75
|
+
# @param modifiers Serialization parameters
|
76
|
+
# @option modifiers [Hash] :only The only attributes to serialize
|
77
|
+
# @option modifiers [Hash] :except Attributes to hide
|
78
|
+
# @option modifiers [Hash] :with Hidden attributes to serialize additionally
|
88
79
|
#
|
89
80
|
# @return [SeregaPlan] Serialization plan
|
90
81
|
#
|
91
|
-
def initialize(
|
92
|
-
@
|
93
|
-
@points = attributes_points(only: only, except: except, with: with)
|
82
|
+
def initialize(modifiers)
|
83
|
+
@points = attributes_points(modifiers)
|
94
84
|
end
|
95
85
|
|
96
86
|
#
|
@@ -102,7 +92,10 @@ class Serega
|
|
102
92
|
|
103
93
|
private
|
104
94
|
|
105
|
-
def attributes_points(
|
95
|
+
def attributes_points(modifiers)
|
96
|
+
only = modifiers[:only] || FROZEN_EMPTY_HASH
|
97
|
+
except = modifiers[:except] || FROZEN_EMPTY_HASH
|
98
|
+
with = modifiers[:with] || FROZEN_EMPTY_HASH
|
106
99
|
points = []
|
107
100
|
|
108
101
|
serializer_class.attributes.each_value do |attribute|
|
@@ -114,7 +107,9 @@ class Serega
|
|
114
107
|
{only: only[name], with: with[name], except: except[name]}
|
115
108
|
end
|
116
109
|
|
117
|
-
|
110
|
+
point = serializer_class::SeregaPlanPoint.new(attribute, child_fields)
|
111
|
+
point.plan = self
|
112
|
+
points << point.freeze
|
118
113
|
end
|
119
114
|
|
120
115
|
points.freeze
|
data/lib/serega/plan_point.rb
CHANGED
@@ -13,7 +13,7 @@ class Serega
|
|
13
13
|
|
14
14
|
# Link to current plan this point belongs to
|
15
15
|
# @return [SeregaAttribute] Current plan
|
16
|
-
|
16
|
+
attr_accessor :plan
|
17
17
|
|
18
18
|
# Shows current attribute
|
19
19
|
# @return [SeregaAttribute] Current attribute
|
@@ -24,8 +24,8 @@ class Serega
|
|
24
24
|
attr_reader :child_plan
|
25
25
|
|
26
26
|
# Child fields to serialize
|
27
|
-
# @return [
|
28
|
-
attr_reader :
|
27
|
+
# @return [Hash] Attributes to serialize
|
28
|
+
attr_reader :modifiers
|
29
29
|
|
30
30
|
# @!method name
|
31
31
|
# Attribute `name`
|
@@ -44,18 +44,18 @@ class Serega
|
|
44
44
|
#
|
45
45
|
# Initializes plan point
|
46
46
|
#
|
47
|
-
# @param plan [SeregaPlan] Plan where this point belongs to.
|
48
47
|
# @param attribute [SeregaAttribute] Attribute to construct plan point
|
49
|
-
# @param
|
48
|
+
# @param modifiers Serialization parameters
|
49
|
+
# @option modifiers [Hash] :only The only attributes to serialize
|
50
|
+
# @option modifiers [Hash] :except Attributes to hide
|
51
|
+
# @option modifiers [Hash] :with Hidden attributes to serialize additionally
|
50
52
|
#
|
51
53
|
# @return [SeregaPlanPoint] New plan point
|
52
54
|
#
|
53
|
-
def initialize(attribute,
|
54
|
-
@plan = plan
|
55
|
+
def initialize(attribute, modifiers = nil)
|
55
56
|
@attribute = attribute
|
56
|
-
@
|
57
|
+
@modifiers = modifiers
|
57
58
|
set_normalized_vars
|
58
|
-
freeze
|
59
59
|
end
|
60
60
|
|
61
61
|
#
|
@@ -77,14 +77,11 @@ class Serega
|
|
77
77
|
def prepare_child_plan
|
78
78
|
return unless serializer
|
79
79
|
|
80
|
-
fields =
|
80
|
+
fields = modifiers || FROZEN_EMPTY_HASH
|
81
81
|
|
82
|
-
serializer::SeregaPlan.new(
|
83
|
-
|
84
|
-
|
85
|
-
with: fields[:with] || FROZEN_EMPTY_HASH,
|
86
|
-
except: fields[:except] || FROZEN_EMPTY_HASH
|
87
|
-
)
|
82
|
+
plan = serializer::SeregaPlan.new(fields)
|
83
|
+
plan.parent_plan_point = self
|
84
|
+
plan
|
88
85
|
end
|
89
86
|
end
|
90
87
|
|
@@ -60,6 +60,10 @@ class Serega
|
|
60
60
|
|
61
61
|
private
|
62
62
|
|
63
|
+
def keys
|
64
|
+
@keys ||= {}
|
65
|
+
end
|
66
|
+
|
63
67
|
def each_key
|
64
68
|
keys.each do |key, containers|
|
65
69
|
containers.each do |container|
|
@@ -74,16 +78,30 @@ class Serega
|
|
74
78
|
def keys_values
|
75
79
|
ids = keys.keys
|
76
80
|
|
77
|
-
|
78
|
-
|
81
|
+
keys_values = load_keys_values(ids)
|
82
|
+
validate(keys_values)
|
79
83
|
|
80
|
-
|
81
|
-
raise SeregaError, "Batch loader for `#{attribute_name}` must return Hash, but #{vals.inspect} was returned"
|
82
|
-
end
|
84
|
+
keys_values
|
83
85
|
end
|
84
86
|
|
85
|
-
def
|
86
|
-
|
87
|
+
def load_keys_values(ids)
|
88
|
+
point.batch[:loader].call(ids, object_serializer.context, point)
|
89
|
+
rescue => error
|
90
|
+
raise reraise_with_serialized_attribute_details(error, point)
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate(keys_values)
|
94
|
+
return if keys_values.is_a?(Hash)
|
95
|
+
|
96
|
+
attribute_name = "#{point.class.serializer_class}.#{point.name}"
|
97
|
+
raise SeregaError, "Batch loader for `#{attribute_name}` must return Hash, but #{keys_values.inspect} was returned"
|
98
|
+
end
|
99
|
+
|
100
|
+
def reraise_with_serialized_attribute_details(error, point)
|
101
|
+
raise error.exception(<<~MESSAGE.strip)
|
102
|
+
#{error.message}
|
103
|
+
(when serializing '#{point.name}' attribute in #{self.class.serializer_class})
|
104
|
+
MESSAGE
|
87
105
|
end
|
88
106
|
end
|
89
107
|
|
@@ -49,9 +49,7 @@ class Serega
|
|
49
49
|
key = batch[:key] || self.class.serializer_class.config.batch.default_key
|
50
50
|
proc_key =
|
51
51
|
if key.is_a?(Symbol)
|
52
|
-
proc
|
53
|
-
handle_no_method_error { object.public_send(key) }
|
54
|
-
end
|
52
|
+
proc { |object| object.public_send(key) }
|
55
53
|
else
|
56
54
|
key
|
57
55
|
end
|
@@ -61,12 +59,6 @@ class Serega
|
|
61
59
|
|
62
60
|
{loader: loader, key: proc_key, default: default}
|
63
61
|
end
|
64
|
-
|
65
|
-
def handle_no_method_error
|
66
|
-
yield
|
67
|
-
rescue NoMethodError => error
|
68
|
-
raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
|
69
|
-
end
|
70
62
|
end
|
71
63
|
end
|
72
64
|
end
|
@@ -194,6 +194,11 @@ class Serega
|
|
194
194
|
next unless metadata
|
195
195
|
|
196
196
|
deep_merge_metadata(hash, metadata)
|
197
|
+
rescue => error
|
198
|
+
raise error.exception(<<~MESSAGE.strip)
|
199
|
+
#{error.message}
|
200
|
+
(when serializing meta_attribute #{meta_attribute.path.inspect} in #{self.class})
|
201
|
+
MESSAGE
|
197
202
|
end
|
198
203
|
end
|
199
204
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module Preloads
|
6
|
+
#
|
7
|
+
# Serega::SeregaAttribute additional/patched instance methods
|
8
|
+
#
|
9
|
+
# @see Serega::SeregaAttribute::AttributeInstanceMethods
|
10
|
+
#
|
11
|
+
module AttributeInstanceMethods
|
12
|
+
# @return [Hash, nil] normalized preloads of current attribute
|
13
|
+
attr_reader :preloads
|
14
|
+
|
15
|
+
# @return [Array] normalized preloads_path of current attribute
|
16
|
+
attr_reader :preloads_path
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def set_normalized_vars(normalizer)
|
21
|
+
super
|
22
|
+
@preloads = normalizer.preloads
|
23
|
+
@preloads_path = normalizer.preloads_path
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module Preloads
|
6
|
+
#
|
7
|
+
# Serega::SeregaAttributeNormalizer additional/patched instance methods
|
8
|
+
#
|
9
|
+
# @see SeregaAttributeNormalizer::AttributeNormalizerInstanceMethods
|
10
|
+
#
|
11
|
+
module AttributeNormalizerInstanceMethods
|
12
|
+
# @return [Hash,nil] normalized attribute preloads
|
13
|
+
def preloads
|
14
|
+
return @preloads if instance_variable_defined?(:@preloads)
|
15
|
+
|
16
|
+
@preloads = prepare_preloads
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array, nil] normalized attribute preloads path
|
20
|
+
def preloads_path
|
21
|
+
return @preloads_path if instance_variable_defined?(:@preloads_path)
|
22
|
+
|
23
|
+
@preloads_path = prepare_preloads_path
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
#
|
29
|
+
# Patched in:
|
30
|
+
# - plugin :batch (extension :preloads - skips auto preloads when batch option provided)
|
31
|
+
#
|
32
|
+
def prepare_preloads
|
33
|
+
opts = init_opts
|
34
|
+
preloads_provided = opts.key?(:preload)
|
35
|
+
preloads =
|
36
|
+
if preloads_provided
|
37
|
+
opts[:preload]
|
38
|
+
elsif opts.key?(:serializer) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_serializer
|
39
|
+
key
|
40
|
+
elsif opts.key?(:delegate) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_delegate
|
41
|
+
opts[:delegate].fetch(:to)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Nil and empty hash differs as we can preload nested results to
|
45
|
+
# empty hash, but we will skip nested preloading if nil or false provided
|
46
|
+
return if preloads_provided && !preloads
|
47
|
+
|
48
|
+
FormatUserPreloads.call(preloads)
|
49
|
+
end
|
50
|
+
|
51
|
+
def prepare_preloads_path
|
52
|
+
path = init_opts.fetch(:preload_path) { default_preload_path(preloads) }
|
53
|
+
|
54
|
+
if path && path[0].is_a?(Array)
|
55
|
+
prepare_many_preload_paths(path)
|
56
|
+
else
|
57
|
+
prepare_one_preload_path(path)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepare_one_preload_path(path)
|
62
|
+
return unless path
|
63
|
+
|
64
|
+
case path
|
65
|
+
when Array
|
66
|
+
path.map(&:to_sym).freeze
|
67
|
+
else
|
68
|
+
[path.to_sym].freeze
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def prepare_many_preload_paths(paths)
|
73
|
+
paths.map { |path| prepare_one_preload_path(path) }.freeze
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_preload_path(preloads)
|
77
|
+
return FROZEN_EMPTY_ARRAY if !preloads || preloads.empty?
|
78
|
+
|
79
|
+
[preloads.keys.first]
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Patch for original `prepare_hide` method
|
84
|
+
# @see
|
85
|
+
#
|
86
|
+
# Marks attribute hidden if auto_hide_attribute_with_preloads option was set and attribute has preloads
|
87
|
+
#
|
88
|
+
def prepare_hide
|
89
|
+
res = super
|
90
|
+
return res unless res.nil?
|
91
|
+
|
92
|
+
if preloads && !preloads.empty?
|
93
|
+
self.class.serializer_class.config.preloads.auto_hide_attributes_with_preload || nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module Preloads
|
6
|
+
#
|
7
|
+
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
8
|
+
#
|
9
|
+
# @see Serega::SeregaValidations::CheckAttributeParams
|
10
|
+
#
|
11
|
+
module CheckAttributeParamsInstanceMethods
|
12
|
+
private
|
13
|
+
|
14
|
+
def check_opts
|
15
|
+
super
|
16
|
+
CheckOptPreload.call(opts)
|
17
|
+
CheckOptPreloadPath.call(opts)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module Preloads
|
6
|
+
#
|
7
|
+
# Config class additional/patched instance methods
|
8
|
+
#
|
9
|
+
# @see Serega::SeregaConfig
|
10
|
+
#
|
11
|
+
module ConfigInstanceMethods
|
12
|
+
# @return [Serega::SeregaPlugins::Preloads::PreloadsConfig] `preloads` plugin config
|
13
|
+
def preloads
|
14
|
+
@preloads ||= PreloadsConfig.new(opts.fetch(:preloads))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module Preloads
|
6
|
+
#
|
7
|
+
# Serega::SeregaPlanPoint additional/patched instance methods
|
8
|
+
#
|
9
|
+
# @see Serega::SeregaPlanPoint::InstanceMethods
|
10
|
+
#
|
11
|
+
module PlanPointInstanceMethods
|
12
|
+
#
|
13
|
+
# @return [Hash] preloads for nested attributes
|
14
|
+
#
|
15
|
+
attr_reader :preloads
|
16
|
+
|
17
|
+
#
|
18
|
+
# @return [Array<Symbol>] preloads path for current attribute
|
19
|
+
#
|
20
|
+
attr_reader :preloads_path
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def set_normalized_vars
|
25
|
+
super
|
26
|
+
|
27
|
+
@preloads = prepare_preloads
|
28
|
+
@preloads_path = prepare_preloads_path
|
29
|
+
end
|
30
|
+
|
31
|
+
def prepare_preloads
|
32
|
+
PreloadsConstructor.call(child_plan)
|
33
|
+
end
|
34
|
+
|
35
|
+
def prepare_preloads_path
|
36
|
+
attribute.preloads_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module Preloads
|
6
|
+
#
|
7
|
+
# Utility that helps to transform preloads to array of paths
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# call({ a: { b: { c: {}, d: {} } }, e: {} })
|
12
|
+
#
|
13
|
+
# => [
|
14
|
+
# [:a],
|
15
|
+
# [:a, :b],
|
16
|
+
# [:a, :b, :c],
|
17
|
+
# [:a, :b, :d],
|
18
|
+
# [:e]
|
19
|
+
# ]
|
20
|
+
class PreloadPaths
|
21
|
+
class << self
|
22
|
+
#
|
23
|
+
# Transforms user provided preloads to array of paths
|
24
|
+
#
|
25
|
+
# @param value [Array,Hash,String,Symbol,nil,false] preloads
|
26
|
+
#
|
27
|
+
# @return [Hash] preloads transformed to hash
|
28
|
+
#
|
29
|
+
def call(preloads, path = [], result = [])
|
30
|
+
preloads = FormatUserPreloads.call(preloads)
|
31
|
+
|
32
|
+
preloads.each do |key, nested_preloads|
|
33
|
+
path << key
|
34
|
+
result << path.dup
|
35
|
+
|
36
|
+
call(nested_preloads, path, result)
|
37
|
+
path.pop
|
38
|
+
end
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module Preloads
|
6
|
+
#
|
7
|
+
# Config for `preloads` plugin
|
8
|
+
#
|
9
|
+
class PreloadsConfig
|
10
|
+
# @return [Hash] preloads plugin options
|
11
|
+
attr_reader :opts
|
12
|
+
|
13
|
+
#
|
14
|
+
# Initializes context_metadata config object
|
15
|
+
#
|
16
|
+
# @param opts [Hash] options
|
17
|
+
#
|
18
|
+
# @return [Serega::SeregaPlugins::Metadata::MetadataConfig]
|
19
|
+
#
|
20
|
+
def initialize(opts)
|
21
|
+
@opts = opts
|
22
|
+
end
|
23
|
+
|
24
|
+
# @!method auto_preload_attributes_with_delegate
|
25
|
+
# @return [Boolean, nil] option value
|
26
|
+
#
|
27
|
+
# @!method auto_preload_attributes_with_delegate=(value)
|
28
|
+
# @param value [Boolean] New option value
|
29
|
+
# @return [Boolean] New option value
|
30
|
+
#
|
31
|
+
# @!method auto_preload_attributes_with_serializer
|
32
|
+
# @return [Boolean, nil] option value
|
33
|
+
#
|
34
|
+
# @!method auto_preload_attributes_with_serializer=(value)
|
35
|
+
# @param value [Boolean] New option value
|
36
|
+
# @return [Boolean] New option value
|
37
|
+
#
|
38
|
+
# @!method auto_hide_attributes_with_preload
|
39
|
+
# @return [Boolean, nil] option value
|
40
|
+
#
|
41
|
+
# @!method auto_hide_attributes_with_preload=(value)
|
42
|
+
# @param value [Boolean] New option value
|
43
|
+
# @return [Boolean] New option value
|
44
|
+
#
|
45
|
+
%i[
|
46
|
+
auto_preload_attributes_with_delegate
|
47
|
+
auto_preload_attributes_with_serializer
|
48
|
+
auto_hide_attributes_with_preload
|
49
|
+
].each do |method_name|
|
50
|
+
define_method(method_name) do
|
51
|
+
opts.fetch(method_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
define_method("#{method_name}=") do |value|
|
55
|
+
raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
|
56
|
+
opts[method_name] = value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -35,8 +35,9 @@ class Serega
|
|
35
35
|
append_current(preloads, current_preloads)
|
36
36
|
next unless child_plan
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
each_child_preloads(preloads, point.preloads_path) do |child_preloads|
|
39
|
+
append_many(child_preloads, child_plan)
|
40
|
+
end
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -50,14 +51,26 @@ class Serega
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
def
|
54
|
-
return
|
54
|
+
def each_child_preloads(preloads, preloads_path)
|
55
|
+
return yield(preloads) if preloads_path.nil?
|
55
56
|
|
56
|
-
|
57
|
-
|
57
|
+
if preloads_path[0].is_a?(Array)
|
58
|
+
preloads_path.each do |path|
|
59
|
+
yield dig_fetch(preloads, path)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
yield dig_fetch(preloads, preloads_path)
|
58
63
|
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def dig_fetch(preloads, preloads_path)
|
67
|
+
return preloads if !preloads_path || preloads_path.empty?
|
59
68
|
|
60
|
-
|
69
|
+
preloads_path.each do |path|
|
70
|
+
preloads = preloads.fetch(path)
|
71
|
+
end
|
72
|
+
|
73
|
+
preloads
|
61
74
|
end
|
62
75
|
end
|
63
76
|
end
|
@@ -79,6 +79,18 @@ class Serega
|
|
79
79
|
# @return [void]
|
80
80
|
#
|
81
81
|
def self.load_plugin(serializer_class, **_opts)
|
82
|
+
require_relative "./lib/format_user_preloads"
|
83
|
+
require_relative "./lib/modules/attribute"
|
84
|
+
require_relative "./lib/modules/attribute_normalizer"
|
85
|
+
require_relative "./lib/modules/check_attribute_params"
|
86
|
+
require_relative "./lib/modules/config"
|
87
|
+
require_relative "./lib/modules/plan_point"
|
88
|
+
require_relative "./lib/preload_paths"
|
89
|
+
require_relative "./lib/preloads_config"
|
90
|
+
require_relative "./lib/preloads_constructor"
|
91
|
+
require_relative "./validations/check_opt_preload"
|
92
|
+
require_relative "./validations/check_opt_preload_path"
|
93
|
+
|
82
94
|
serializer_class.include(InstanceMethods)
|
83
95
|
serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
|
84
96
|
serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
|
@@ -86,12 +98,6 @@ class Serega
|
|
86
98
|
serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
|
87
99
|
|
88
100
|
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
89
|
-
|
90
|
-
require_relative "./lib/format_user_preloads"
|
91
|
-
require_relative "./lib/main_preload_path"
|
92
|
-
require_relative "./lib/preloads_constructor"
|
93
|
-
require_relative "./validations/check_opt_preload"
|
94
|
-
require_relative "./validations/check_opt_preload_path"
|
95
101
|
end
|
96
102
|
|
97
103
|
#
|
@@ -125,210 +131,6 @@ class Serega
|
|
125
131
|
@preloads ||= PreloadsConstructor.call(plan)
|
126
132
|
end
|
127
133
|
end
|
128
|
-
|
129
|
-
#
|
130
|
-
# Config for `preloads` plugin
|
131
|
-
#
|
132
|
-
class PreloadsConfig
|
133
|
-
# @return [Hash] preloads plugin options
|
134
|
-
attr_reader :opts
|
135
|
-
|
136
|
-
#
|
137
|
-
# Initializes context_metadata config object
|
138
|
-
#
|
139
|
-
# @param opts [Hash] options
|
140
|
-
#
|
141
|
-
# @return [Serega::SeregaPlugins::Metadata::MetadataConfig]
|
142
|
-
#
|
143
|
-
def initialize(opts)
|
144
|
-
@opts = opts
|
145
|
-
end
|
146
|
-
|
147
|
-
# @!method auto_preload_attributes_with_delegate
|
148
|
-
# @return [Boolean, nil] option value
|
149
|
-
#
|
150
|
-
# @!method auto_preload_attributes_with_delegate=(value)
|
151
|
-
# @param value [Boolean] New option value
|
152
|
-
# @return [Boolean] New option value
|
153
|
-
#
|
154
|
-
# @!method auto_preload_attributes_with_serializer
|
155
|
-
# @return [Boolean, nil] option value
|
156
|
-
#
|
157
|
-
# @!method auto_preload_attributes_with_serializer=(value)
|
158
|
-
# @param value [Boolean] New option value
|
159
|
-
# @return [Boolean] New option value
|
160
|
-
#
|
161
|
-
# @!method auto_hide_attributes_with_preload
|
162
|
-
# @return [Boolean, nil] option value
|
163
|
-
#
|
164
|
-
# @!method auto_hide_attributes_with_preload=(value)
|
165
|
-
# @param value [Boolean] New option value
|
166
|
-
# @return [Boolean] New option value
|
167
|
-
#
|
168
|
-
%i[
|
169
|
-
auto_preload_attributes_with_delegate
|
170
|
-
auto_preload_attributes_with_serializer
|
171
|
-
auto_hide_attributes_with_preload
|
172
|
-
].each do |method_name|
|
173
|
-
define_method(method_name) do
|
174
|
-
opts.fetch(method_name)
|
175
|
-
end
|
176
|
-
|
177
|
-
define_method("#{method_name}=") do |value|
|
178
|
-
raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
|
179
|
-
opts[method_name] = value
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
#
|
185
|
-
# Config class additional/patched instance methods
|
186
|
-
#
|
187
|
-
# @see Serega::SeregaConfig
|
188
|
-
#
|
189
|
-
module ConfigInstanceMethods
|
190
|
-
# @return [Serega::SeregaPlugins::Preloads::PreloadsConfig] `preloads` plugin config
|
191
|
-
def preloads
|
192
|
-
@preloads ||= PreloadsConfig.new(opts.fetch(:preloads))
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
#
|
197
|
-
# Serega::SeregaAttribute additional/patched instance methods
|
198
|
-
#
|
199
|
-
# @see Serega::SeregaAttribute::AttributeInstanceMethods
|
200
|
-
#
|
201
|
-
module AttributeInstanceMethods
|
202
|
-
# @return [Hash, nil] normalized preloads of current attribute
|
203
|
-
attr_reader :preloads
|
204
|
-
|
205
|
-
# @return [Array] normalized preloads_path of current attribute
|
206
|
-
attr_reader :preloads_path
|
207
|
-
|
208
|
-
private
|
209
|
-
|
210
|
-
def set_normalized_vars(normalizer)
|
211
|
-
super
|
212
|
-
@preloads = normalizer.preloads
|
213
|
-
@preloads_path = normalizer.preloads_path
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
#
|
218
|
-
# SeregaAttributeNormalizer additional/patched instance methods
|
219
|
-
#
|
220
|
-
# @see SeregaAttributeNormalizer::AttributeInstanceMethods
|
221
|
-
#
|
222
|
-
module AttributeNormalizerInstanceMethods
|
223
|
-
# @return [Hash,nil] normalized attribute preloads
|
224
|
-
def preloads
|
225
|
-
return @preloads if instance_variable_defined?(:@preloads)
|
226
|
-
|
227
|
-
@preloads = prepare_preloads
|
228
|
-
end
|
229
|
-
|
230
|
-
# @return [Array, nil] normalized attribute preloads path
|
231
|
-
def preloads_path
|
232
|
-
return @preloads_path if instance_variable_defined?(:@preloads_path)
|
233
|
-
|
234
|
-
@preloads_path = prepare_preloads_path
|
235
|
-
end
|
236
|
-
|
237
|
-
private
|
238
|
-
|
239
|
-
#
|
240
|
-
# Patched in:
|
241
|
-
# - plugin :batch (extension :preloads - skips auto preloads when batch option provided)
|
242
|
-
#
|
243
|
-
def prepare_preloads
|
244
|
-
opts = init_opts
|
245
|
-
preloads_provided = opts.key?(:preload)
|
246
|
-
preloads =
|
247
|
-
if preloads_provided
|
248
|
-
opts[:preload]
|
249
|
-
elsif opts.key?(:serializer) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_serializer
|
250
|
-
key
|
251
|
-
elsif opts.key?(:delegate) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_delegate
|
252
|
-
opts[:delegate].fetch(:to)
|
253
|
-
end
|
254
|
-
|
255
|
-
# Nil and empty hash differs as we can preload nested results to
|
256
|
-
# empty hash, but we will skip nested preloading if nil or false provided
|
257
|
-
return if preloads_provided && !preloads
|
258
|
-
|
259
|
-
FormatUserPreloads.call(preloads)
|
260
|
-
end
|
261
|
-
|
262
|
-
def prepare_preloads_path
|
263
|
-
opts = init_opts
|
264
|
-
path = Array(opts[:preload_path]).map!(&:to_sym).freeze
|
265
|
-
path = MainPreloadPath.call(preloads) if path.empty?
|
266
|
-
path
|
267
|
-
end
|
268
|
-
|
269
|
-
#
|
270
|
-
# Patch for original `prepare_hide` method
|
271
|
-
#
|
272
|
-
# Marks attribute hidden if auto_hide_attribute_with_preloads option was set and attribute has preloads
|
273
|
-
#
|
274
|
-
def prepare_hide
|
275
|
-
res = super
|
276
|
-
return res unless res.nil?
|
277
|
-
|
278
|
-
if preloads && !preloads.empty?
|
279
|
-
self.class.serializer_class.config.preloads.auto_hide_attributes_with_preload || nil
|
280
|
-
end
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
#
|
285
|
-
# Serega::SeregaPlanPoint additional/patched instance methods
|
286
|
-
#
|
287
|
-
# @see Serega::SeregaPlanPoint::InstanceMethods
|
288
|
-
#
|
289
|
-
module PlanPointInstanceMethods
|
290
|
-
#
|
291
|
-
# @return [Hash] preloads for nested attributes
|
292
|
-
#
|
293
|
-
attr_reader :preloads
|
294
|
-
|
295
|
-
#
|
296
|
-
# @return [Array<Symbol>] preloads path for current attribute
|
297
|
-
#
|
298
|
-
attr_reader :preloads_path
|
299
|
-
|
300
|
-
private
|
301
|
-
|
302
|
-
def set_normalized_vars
|
303
|
-
super
|
304
|
-
|
305
|
-
@preloads = prepare_preloads
|
306
|
-
@preloads_path = prepare_preloads_path
|
307
|
-
end
|
308
|
-
|
309
|
-
def prepare_preloads
|
310
|
-
PreloadsConstructor.call(child_plan)
|
311
|
-
end
|
312
|
-
|
313
|
-
def prepare_preloads_path
|
314
|
-
attribute.preloads_path
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
#
|
319
|
-
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
320
|
-
#
|
321
|
-
# @see Serega::SeregaValidations::CheckAttributeParams
|
322
|
-
#
|
323
|
-
module CheckAttributeParamsInstanceMethods
|
324
|
-
private
|
325
|
-
|
326
|
-
def check_opts
|
327
|
-
super
|
328
|
-
CheckOptPreload.call(opts)
|
329
|
-
CheckOptPreloadPath.call(opts)
|
330
|
-
end
|
331
|
-
end
|
332
134
|
end
|
333
135
|
|
334
136
|
register_plugin(Preloads.plugin_name, Preloads)
|
@@ -18,30 +18,69 @@ class Serega
|
|
18
18
|
# @return [void]
|
19
19
|
#
|
20
20
|
def call(opts)
|
21
|
-
return
|
21
|
+
return if exactly_nil?(opts, :preload_path) # allow to provide nil anyway
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
path = opts[:preload_path]
|
24
|
+
check_usage_with_other_options(path, opts)
|
25
|
+
return unless opts[:serializer]
|
26
26
|
|
27
|
-
path
|
28
|
-
preloads = FormatUserPreloads.call(opts[:preload])
|
29
|
-
allowed_paths = paths(preloads)
|
30
|
-
raise SeregaError, "Invalid option :preload_path => #{value.inspect}. Can be one of #{allowed_paths.inspect[1..-2]}" unless allowed_paths.include?(path)
|
27
|
+
check_allowed(path, opts)
|
31
28
|
end
|
32
29
|
|
33
30
|
private
|
34
31
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
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 = 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
|
39
47
|
|
40
|
-
|
41
|
-
|
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)
|
42
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
|
43
80
|
|
44
|
-
|
81
|
+
# Check value is Array in Array
|
82
|
+
def multiple_preload_paths_provided?(value)
|
83
|
+
value.is_a?(Array) && value[0].is_a?(Array)
|
45
84
|
end
|
46
85
|
end
|
47
86
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: serega
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrey Glushkov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
JSON Serializer
|
@@ -73,7 +73,13 @@ files:
|
|
73
73
|
- lib/serega/plugins/metadata/validations/check_opts.rb
|
74
74
|
- lib/serega/plugins/metadata/validations/check_path.rb
|
75
75
|
- lib/serega/plugins/preloads/lib/format_user_preloads.rb
|
76
|
-
- lib/serega/plugins/preloads/lib/
|
76
|
+
- lib/serega/plugins/preloads/lib/modules/attribute.rb
|
77
|
+
- lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb
|
78
|
+
- lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb
|
79
|
+
- lib/serega/plugins/preloads/lib/modules/config.rb
|
80
|
+
- lib/serega/plugins/preloads/lib/modules/plan_point.rb
|
81
|
+
- lib/serega/plugins/preloads/lib/preload_paths.rb
|
82
|
+
- lib/serega/plugins/preloads/lib/preloads_config.rb
|
77
83
|
- lib/serega/plugins/preloads/lib/preloads_constructor.rb
|
78
84
|
- lib/serega/plugins/preloads/preloads.rb
|
79
85
|
- lib/serega/plugins/preloads/validations/check_opt_preload.rb
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Serega
|
4
|
-
module SeregaPlugins
|
5
|
-
module Preloads
|
6
|
-
#
|
7
|
-
# Class that constructs main preloads path.
|
8
|
-
#
|
9
|
-
# When we have nested preloads we will use this path to dig to `main` element and
|
10
|
-
# assign nested preloads to it.
|
11
|
-
#
|
12
|
-
# By default its a path to latest provided preload
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
# MainPreloadPath.(a: { b: { c: {} }, d: {} }) # => [:a, :d]
|
16
|
-
#
|
17
|
-
class MainPreloadPath
|
18
|
-
class << self
|
19
|
-
# Finds default preload path
|
20
|
-
#
|
21
|
-
# @param preloads [Hash] Formatted user provided preloads hash
|
22
|
-
#
|
23
|
-
# @return [Array<Symbol>] Preloads path to `main` element
|
24
|
-
def call(preloads)
|
25
|
-
return FROZEN_EMPTY_ARRAY if !preloads || preloads.empty?
|
26
|
-
|
27
|
-
main_path(preloads).freeze
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
# Generates path (Array) to the last included resource.
|
33
|
-
# We need to know this path to include nested associations.
|
34
|
-
#
|
35
|
-
# main_path(a: { b: { c: {} }, d: {} }) # => [:a, :d]
|
36
|
-
#
|
37
|
-
def main_path(hash, path = [])
|
38
|
-
current_level = path.size
|
39
|
-
|
40
|
-
hash.each do |key, data|
|
41
|
-
path.pop(path.size - current_level)
|
42
|
-
path << key
|
43
|
-
|
44
|
-
main_path(data, path)
|
45
|
-
end
|
46
|
-
|
47
|
-
path
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|