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.
- checksums.yaml +4 -4
- data/README.md +113 -229
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +31 -3
- data/lib/serega/attribute_normalizer.rb +143 -42
- data/lib/serega/batch/attribute_loader.rb +70 -0
- data/lib/serega/batch/attribute_loaders.rb +59 -0
- data/lib/serega/batch/auto_resolver.rb +24 -0
- data/lib/serega/batch/auto_resolver_factory.rb +48 -0
- data/lib/serega/batch/loader.rb +67 -0
- data/lib/serega/config.rb +59 -1
- data/lib/serega/object_serializer.rb +11 -9
- data/lib/serega/plan.rb +16 -0
- data/lib/serega/plan_point.rb +31 -5
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +25 -13
- data/lib/serega/plugins/activerecord_preloads/lib/active_record_objects.rb +31 -0
- data/lib/serega/plugins/camel_case/camel_case.rb +1 -1
- data/lib/serega/plugins/formatters/formatters.rb +79 -22
- data/lib/serega/plugins/if/if.rb +41 -22
- data/lib/serega/plugins/if/validations/check_opt_if.rb +27 -6
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +27 -6
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +27 -6
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +27 -6
- data/lib/serega/plugins/metadata/meta_attribute.rb +9 -10
- data/lib/serega/plugins/metadata/validations/check_block.rb +2 -4
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +2 -3
- data/lib/serega/plugins/presenter/presenter.rb +1 -1
- data/lib/serega/utils/format_user_preloads.rb +58 -0
- data/lib/serega/utils/method_signature.rb +89 -0
- data/lib/serega/utils/preload_paths.rb +53 -0
- data/lib/serega/utils/preloads_constructor.rb +77 -0
- data/lib/serega/validations/attribute/check_block.rb +38 -6
- data/lib/serega/validations/attribute/check_opt_batch.rb +101 -0
- data/lib/serega/validations/attribute/check_opt_const.rb +1 -0
- data/lib/serega/validations/attribute/check_opt_delegate.rb +1 -0
- data/lib/serega/validations/attribute/check_opt_method.rb +1 -0
- data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload.rb +2 -2
- data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload_path.rb +3 -3
- data/lib/serega/validations/attribute/check_opt_value.rb +35 -9
- data/lib/serega/validations/check_attribute_params.rb +3 -2
- data/lib/serega/validations/check_batch_loader_params.rb +80 -0
- data/lib/serega/validations/initiate/check_modifiers.rb +1 -1
- data/lib/serega.rb +98 -7
- metadata +17 -37
- data/lib/serega/plugins/batch/batch.rb +0 -168
- data/lib/serega/plugins/batch/lib/batch_config.rb +0 -77
- data/lib/serega/plugins/batch/lib/loader.rb +0 -113
- data/lib/serega/plugins/batch/lib/loaders.rb +0 -45
- data/lib/serega/plugins/batch/lib/modules/attribute.rb +0 -26
- data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +0 -78
- data/lib/serega/plugins/batch/lib/modules/check_attribute_params.rb +0 -22
- data/lib/serega/plugins/batch/lib/modules/config.rb +0 -23
- data/lib/serega/plugins/batch/lib/modules/object_serializer.rb +0 -46
- data/lib/serega/plugins/batch/lib/modules/plan_point.rb +0 -22
- data/lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb +0 -43
- data/lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb +0 -54
- data/lib/serega/plugins/batch/lib/plugins_extensions/if.rb +0 -31
- data/lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb +0 -34
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb +0 -43
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +0 -59
- data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +0 -62
- data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +0 -60
- data/lib/serega/plugins/preloads/lib/modules/attribute.rb +0 -28
- data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +0 -99
- data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +0 -22
- data/lib/serega/plugins/preloads/lib/modules/config.rb +0 -19
- data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +0 -41
- data/lib/serega/plugins/preloads/lib/preload_paths.rb +0 -53
- data/lib/serega/plugins/preloads/lib/preloads_config.rb +0 -62
- data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +0 -79
- data/lib/serega/plugins/preloads/preloads.rb +0 -162
- data/lib/serega/utils/params_count.rb +0 -50
- 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
|
-
|
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
|
-
|
105
|
-
init_name.to_sym
|
119
|
+
@preloads = prepare_preloads
|
106
120
|
end
|
107
121
|
|
108
122
|
#
|
109
|
-
#
|
110
|
-
# - plugin :formatters (wraps resulted block in formatter block and formats :const values)
|
123
|
+
# Shows normalized preloads_path for current attribute
|
111
124
|
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
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
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
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
|
159
|
-
|
160
|
-
|
205
|
+
def prepare_batch_loader_block
|
206
|
+
batch_opt = init_opts[:batch]
|
207
|
+
return unless batch_opt
|
161
208
|
|
162
|
-
|
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
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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) {
|
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[
|
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
|
-
|
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
|
-
|
64
|
-
|
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)
|