serega 0.42.0 → 0.50.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 +124 -1207
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +186 -131
- data/lib/serega/attribute_normalizer.rb +164 -345
- data/lib/serega/attribute_normalizers/batch.rb +45 -0
- data/lib/serega/attribute_normalizers/block.rb +81 -0
- data/lib/serega/attribute_normalizers/delegate.rb +34 -0
- data/lib/serega/attribute_normalizers/hash_access.rb +37 -0
- data/lib/serega/attribute_normalizers/preloads.rb +46 -0
- data/lib/serega/attribute_value_resolvers/batch.rb +5 -54
- data/lib/serega/attribute_value_resolvers/const.rb +2 -24
- data/lib/serega/attribute_value_resolvers/delegate.rb +17 -28
- data/lib/serega/attribute_value_resolvers/hash_access.rb +34 -52
- data/lib/serega/attribute_value_resolvers/keyword.rb +10 -25
- data/lib/serega/attribute_values.rb +46 -0
- data/lib/serega/batch_loader.rb +37 -0
- data/lib/serega/conditions.rb +56 -0
- data/lib/serega/config.rb +105 -144
- data/lib/serega/engine/pulls.rb +64 -0
- data/lib/serega/engine/run.rb +106 -0
- data/lib/serega/errors.rb +36 -8
- data/lib/serega/formatter.rb +98 -0
- data/lib/serega/plan/cache.rb +96 -0
- data/lib/serega/plan/per_ractor_cache.rb +23 -0
- data/lib/serega/plan.rb +101 -128
- data/lib/serega/plan_point.rb +44 -151
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +13 -80
- data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +8 -75
- data/lib/serega/plugins/context_metadata/context_metadata.rb +40 -123
- data/lib/serega/plugins/metadata/check_params.rb +80 -0
- data/lib/serega/plugins/metadata/meta_attribute.rb +40 -79
- data/lib/serega/plugins/metadata/metadata.rb +40 -168
- data/lib/serega/plugins/root/root.rb +91 -235
- data/lib/serega/plugins.rb +16 -49
- data/lib/serega/presenter.rb +61 -19
- data/lib/serega/result_builder.rb +68 -0
- data/lib/serega/result_code.rb +203 -0
- data/lib/serega/shareable.rb +52 -0
- data/lib/serega/source_group.rb +187 -0
- data/lib/serega/utils/collection_detector.rb +19 -13
- data/lib/serega/utils/deep_merge.rb +26 -0
- data/lib/serega/utils/enum_deep_dup.rb +10 -16
- data/lib/serega/utils/enum_deep_freeze.rb +8 -14
- data/lib/serega/utils/method_signature.rb +6 -15
- data/lib/serega/utils/parse_string_modifiers.rb +63 -0
- data/lib/serega/utils/ractor_local.rb +36 -0
- data/lib/serega/utils/serialized_attribute_error.rb +60 -20
- data/lib/serega/utils/to_hash.rb +5 -26
- data/lib/serega/validations/attribute/check_block.rb +3 -15
- data/lib/serega/validations/attribute/check_name.rb +4 -15
- data/lib/serega/validations/attribute/check_opt_base_serializer.rb +3 -15
- data/lib/serega/validations/attribute/check_opt_batch.rb +26 -56
- data/lib/serega/validations/attribute/check_opt_condition.rb +55 -0
- data/lib/serega/validations/attribute/check_opt_delegate.rb +10 -63
- data/lib/serega/validations/attribute/check_opt_format.rb +36 -0
- data/lib/serega/validations/attribute/check_opt_hash_access.rb +11 -59
- data/lib/serega/validations/attribute/check_opt_hide.rb +3 -14
- data/lib/serega/validations/attribute/check_opt_many.rb +18 -20
- data/lib/serega/validations/attribute/check_opt_method.rb +3 -25
- data/lib/serega/validations/attribute/check_opt_preload.rb +3 -26
- data/lib/serega/validations/attribute/check_opt_serializer.rb +4 -18
- data/lib/serega/validations/attribute/check_opt_value.rb +3 -21
- data/lib/serega/validations/check_attribute_params.rb +92 -71
- data/lib/serega/validations/check_batch_loader_params.rb +43 -62
- data/lib/serega/validations/check_formatter.rb +80 -0
- data/lib/serega/validations/check_serialize_keywords.rb +23 -0
- data/lib/serega/validations/initiate/check_modifiers.rb +14 -20
- data/lib/serega/validations/utils/check_allowed_keys.rb +3 -13
- data/lib/serega/validations/utils/check_hash_access_value.rb +44 -0
- data/lib/serega/validations/utils/check_opt_is_bool.rb +3 -13
- data/lib/serega/validations/utils/check_opt_is_hash.rb +3 -13
- data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +3 -13
- data/lib/serega/version.rb +1 -4
- data/lib/serega.rb +459 -388
- data/sig/serega/plugins.rbs +49 -0
- data/sig/serega.rbs +142 -0
- metadata +42 -38
- data/lib/serega/data_builder.rb +0 -54
- data/lib/serega/engine/level.rb +0 -59
- data/lib/serega/engine/level_queue.rb +0 -56
- data/lib/serega/engine/loader.rb +0 -70
- data/lib/serega/helpers/serializer_class_helper.rb +0 -19
- data/lib/serega/object_serializer.rb +0 -125
- data/lib/serega/plugins/camel_case/camel_case.rb +0 -213
- data/lib/serega/plugins/depth_limit/depth_limit.rb +0 -206
- data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +0 -69
- data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +0 -38
- data/lib/serega/plugins/formatters/formatters.rb +0 -322
- data/lib/serega/plugins/if/if.rb +0 -287
- data/lib/serega/plugins/if/validations/check_opt_if.rb +0 -72
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +0 -77
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +0 -72
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +0 -77
- data/lib/serega/plugins/metadata/validations/check_block.rb +0 -47
- data/lib/serega/plugins/metadata/validations/check_opt_const.rb +0 -39
- data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +0 -35
- data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +0 -35
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +0 -61
- data/lib/serega/plugins/metadata/validations/check_opts.rb +0 -59
- data/lib/serega/plugins/metadata/validations/check_path.rb +0 -50
- data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +0 -111
- data/lib/serega/plugins/string_modifiers/string_modifiers.rb +0 -63
- data/lib/serega/utils/symbol_name.rb +0 -39
- data/lib/serega/validations/attribute/check_opt_const.rb +0 -40
- data/lib/serega/validations/check_initiate_params.rb +0 -57
- data/lib/serega/validations/check_serialize_params.rb +0 -54
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaPlugins
|
|
5
|
-
#
|
|
6
|
-
# Plugin :formatters
|
|
7
|
-
#
|
|
8
|
-
# Defines named value formatters once and applies them to any attribute.
|
|
9
|
-
#
|
|
10
|
-
# Use `config.formatters.add()` to register formatters. The `:format`
|
|
11
|
-
# attribute option then accepts a formatter name or a callable directly.
|
|
12
|
-
#
|
|
13
|
-
# Formatters receive up to 2 parameters: the value and the context.
|
|
14
|
-
#
|
|
15
|
-
# @example
|
|
16
|
-
# class AppSerializer < Serega
|
|
17
|
-
# plugin :formatters, formatters: {
|
|
18
|
-
# iso8601: ->(value) { value.iso8601 },
|
|
19
|
-
# on_off: ->(value) { value ? 'ON' : 'OFF' },
|
|
20
|
-
# money: ->(value) { value.round(2) },
|
|
21
|
-
# date: DateTypeFormatter # callable
|
|
22
|
-
# }
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# class UserSerializer < Serega
|
|
26
|
-
# # Additionally we can add formatters via config in subclasses
|
|
27
|
-
# config.formatters.add(
|
|
28
|
-
# iso8601: ->(value) { value.iso8601 },
|
|
29
|
-
# on_off: ->(value) { value ? 'ON' : 'OFF' },
|
|
30
|
-
# money: ->(value) { value.round(2) }
|
|
31
|
-
# )
|
|
32
|
-
#
|
|
33
|
-
# # Using predefined formatter
|
|
34
|
-
# attribute :commission, format: :money
|
|
35
|
-
# attribute :is_logged_in, format: :on_off
|
|
36
|
-
# attribute :created_at, format: :iso8601
|
|
37
|
-
# attribute :updated_at, format: :iso8601
|
|
38
|
-
#
|
|
39
|
-
# # Using `callable` formatter
|
|
40
|
-
# attribute :score_percent, format: PercentFormatter # callable class
|
|
41
|
-
# attribute :score_percent, format: proc { |percent| "#{percent.round(2)}%" }
|
|
42
|
-
# end
|
|
43
|
-
#
|
|
44
|
-
module Formatters
|
|
45
|
-
# @return [Symbol] Plugin name
|
|
46
|
-
# @private
|
|
47
|
-
def self.plugin_name
|
|
48
|
-
:formatters
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Checks requirements and loads additional plugins
|
|
52
|
-
#
|
|
53
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
54
|
-
# @param opts [Hash] Plugin options
|
|
55
|
-
#
|
|
56
|
-
# @return [void]
|
|
57
|
-
#
|
|
58
|
-
# @private
|
|
59
|
-
def self.before_load_plugin(serializer_class, **opts)
|
|
60
|
-
allowed_keys = %i[formatters]
|
|
61
|
-
opts.each_key do |key|
|
|
62
|
-
next if allowed_keys.include?(key)
|
|
63
|
-
|
|
64
|
-
raise SeregaError,
|
|
65
|
-
"Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
|
|
66
|
-
" - :formatters [Hash<Symbol, #call>] - Formatters (names and according callable values)"
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
#
|
|
71
|
-
# Applies plugin code to specific serializer
|
|
72
|
-
#
|
|
73
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
74
|
-
# @param _opts [Hash] Plugin options
|
|
75
|
-
#
|
|
76
|
-
# @return [void]
|
|
77
|
-
#
|
|
78
|
-
# @private
|
|
79
|
-
def self.load_plugin(serializer_class, **_opts)
|
|
80
|
-
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
|
81
|
-
serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
|
|
82
|
-
serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
|
|
83
|
-
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
# Adds config options and runs other callbacks after plugin was loaded
|
|
88
|
-
#
|
|
89
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
90
|
-
# @param opts [Hash] Plugin options
|
|
91
|
-
#
|
|
92
|
-
# @return [void]
|
|
93
|
-
#
|
|
94
|
-
# @private
|
|
95
|
-
def self.after_load_plugin(serializer_class, **opts)
|
|
96
|
-
config = serializer_class.config
|
|
97
|
-
config.opts[:formatters] = {}
|
|
98
|
-
config.formatters.add(opts[:formatters] || {})
|
|
99
|
-
config.attribute_keys << :format
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Formatters plugin config
|
|
103
|
-
class FormattersConfig
|
|
104
|
-
attr_reader :opts
|
|
105
|
-
|
|
106
|
-
#
|
|
107
|
-
# Initializes formatters config object
|
|
108
|
-
#
|
|
109
|
-
# @param opts [Hash] options
|
|
110
|
-
#
|
|
111
|
-
# @return FormattersConfig
|
|
112
|
-
def initialize(opts)
|
|
113
|
-
@opts = opts
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Adds new formatters
|
|
117
|
-
#
|
|
118
|
-
# @param formatters [Hash<Symbol, #call>] hash key is a formatter name and
|
|
119
|
-
# hash value is a callable instance to format value
|
|
120
|
-
#
|
|
121
|
-
# @return [void]
|
|
122
|
-
def add(formatters)
|
|
123
|
-
formatters.each_pair do |key, value|
|
|
124
|
-
CheckFormatter.call(key, value)
|
|
125
|
-
opts[key] = value
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
#
|
|
131
|
-
# Config class additional/patched instance methods
|
|
132
|
-
#
|
|
133
|
-
# @see SeregaConfig
|
|
134
|
-
#
|
|
135
|
-
module ConfigInstanceMethods
|
|
136
|
-
# @return [SeregaPlugins::Formatters::FormattersConfig] current formatters config
|
|
137
|
-
def formatters
|
|
138
|
-
@formatters ||= FormattersConfig.new(opts.fetch(:formatters))
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
#
|
|
143
|
-
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
|
144
|
-
#
|
|
145
|
-
# @see Serega::SeregaValidations::CheckAttributeParams
|
|
146
|
-
#
|
|
147
|
-
# @private
|
|
148
|
-
module CheckAttributeParamsInstanceMethods
|
|
149
|
-
private
|
|
150
|
-
|
|
151
|
-
def check_opts
|
|
152
|
-
super
|
|
153
|
-
|
|
154
|
-
CheckOptFormat.call(opts, self.class.serializer_class)
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
#
|
|
159
|
-
# AttributeNormalizer class additional/patched instance methods
|
|
160
|
-
#
|
|
161
|
-
# @see SeregaAttributeNormalizer
|
|
162
|
-
#
|
|
163
|
-
# @private
|
|
164
|
-
module AttributeNormalizerInstanceMethods
|
|
165
|
-
# Block or callable instance that will format attribute values
|
|
166
|
-
# @return [Proc, #call, nil] Block or callable instance that will format attribute values
|
|
167
|
-
def formatter
|
|
168
|
-
@formatter ||= prepare_formatter
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
#
|
|
172
|
-
# Returns formatter method signature for optimized calling
|
|
173
|
-
#
|
|
174
|
-
# @return [String, nil] Formatter signature ("1", "2", "1_ctx") or nil if no formatter
|
|
175
|
-
#
|
|
176
|
-
def formatter_signature
|
|
177
|
-
@formatter_signature ||= prepare_formatter_signature
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
private
|
|
181
|
-
|
|
182
|
-
def prepare_formatter
|
|
183
|
-
formatter = init_opts[:format]
|
|
184
|
-
return unless formatter
|
|
185
|
-
|
|
186
|
-
formatter = self.class.serializer_class.config.formatters.opts.fetch(formatter) if formatter.is_a?(Symbol)
|
|
187
|
-
formatter
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def prepare_formatter_signature
|
|
191
|
-
return unless formatter
|
|
192
|
-
|
|
193
|
-
SeregaUtils::MethodSignature.call(formatter, pos_limit: 2, keyword_args: [:ctx])
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
#
|
|
198
|
-
# Attribute class additional/patched instance methods
|
|
199
|
-
#
|
|
200
|
-
# @see SeregaAttribute
|
|
201
|
-
#
|
|
202
|
-
# @private
|
|
203
|
-
module AttributeInstanceMethods
|
|
204
|
-
#
|
|
205
|
-
# Returns formatted attribute value
|
|
206
|
-
#
|
|
207
|
-
# @param object [Object] Serialized object
|
|
208
|
-
# @param context [Hash] Serialization context
|
|
209
|
-
#
|
|
210
|
-
# @return [Object] Formatted attribute value
|
|
211
|
-
#
|
|
212
|
-
def value(object, context, batches: nil)
|
|
213
|
-
result = super # `:batches` parameter is needed to find unformatted result
|
|
214
|
-
return result unless formatter
|
|
215
|
-
|
|
216
|
-
case formatter_signature
|
|
217
|
-
when "1" then formatter.call(result)
|
|
218
|
-
when "1_ctx" then formatter.call(result, ctx: context)
|
|
219
|
-
else # "2"
|
|
220
|
-
formatter.call(result, context)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
private
|
|
225
|
-
|
|
226
|
-
attr_reader :formatter, :formatter_signature
|
|
227
|
-
|
|
228
|
-
def set_normalized_vars(normalizer)
|
|
229
|
-
super
|
|
230
|
-
@formatter = normalizer.formatter
|
|
231
|
-
@formatter_signature = normalizer.formatter_signature
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
#
|
|
236
|
-
# Validator for attribute :format option
|
|
237
|
-
#
|
|
238
|
-
# @private
|
|
239
|
-
class CheckOptFormat
|
|
240
|
-
class << self
|
|
241
|
-
#
|
|
242
|
-
# Checks attribute :format option must be registered or valid callable with maximum 2 args
|
|
243
|
-
#
|
|
244
|
-
# @param opts [value] Attribute options
|
|
245
|
-
#
|
|
246
|
-
# @raise [SeregaError] Attribute validation error
|
|
247
|
-
#
|
|
248
|
-
# @return [void]
|
|
249
|
-
#
|
|
250
|
-
def call(opts, serializer_class)
|
|
251
|
-
return unless opts.key?(:format)
|
|
252
|
-
|
|
253
|
-
formatter = opts[:format]
|
|
254
|
-
|
|
255
|
-
if formatter.is_a?(Symbol)
|
|
256
|
-
check_formatter_defined(serializer_class, formatter)
|
|
257
|
-
else
|
|
258
|
-
CheckFormatter.call(:format, formatter)
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
private
|
|
263
|
-
|
|
264
|
-
def check_formatter_defined(serializer_class, formatter)
|
|
265
|
-
return if serializer_class.config.formatters.opts.key?(formatter)
|
|
266
|
-
|
|
267
|
-
raise Serega::SeregaError, "Formatter `#{formatter.inspect}` was not defined"
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
#
|
|
273
|
-
# Validator for formatters defined as config options or directly as attribute :format option
|
|
274
|
-
#
|
|
275
|
-
# @private
|
|
276
|
-
class CheckFormatter
|
|
277
|
-
class << self
|
|
278
|
-
#
|
|
279
|
-
# Check formatter type and parameters
|
|
280
|
-
#
|
|
281
|
-
# @param formatter_name [Symbol] Name of formatter
|
|
282
|
-
# @param formatter [#call] Formatter callable object
|
|
283
|
-
#
|
|
284
|
-
# @return [void]
|
|
285
|
-
#
|
|
286
|
-
def call(formatter_name, formatter)
|
|
287
|
-
raise Serega::SeregaError, "Option #{formatter_name.inspect} must have callable value" unless formatter.respond_to?(:call)
|
|
288
|
-
|
|
289
|
-
signature = SeregaUtils::MethodSignature.call(formatter, pos_limit: 2, keyword_args: [:ctx])
|
|
290
|
-
raise SeregaError, signature_error unless valid_signature?(signature)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
private
|
|
294
|
-
|
|
295
|
-
def valid_signature?(signature)
|
|
296
|
-
case signature
|
|
297
|
-
when "1" # (object)
|
|
298
|
-
true
|
|
299
|
-
when "2" # (object, context)
|
|
300
|
-
true
|
|
301
|
-
when "1_ctx" # (object, :ctx)
|
|
302
|
-
true
|
|
303
|
-
else
|
|
304
|
-
false
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def signature_error
|
|
309
|
-
<<~ERROR.strip
|
|
310
|
-
Invalid formatter parameters, valid parameters signatures:
|
|
311
|
-
- (object) # one positional parameter
|
|
312
|
-
- (object, context) # two positional parameters
|
|
313
|
-
- (object, :ctx) # one positional parameter and :ctx keyword
|
|
314
|
-
ERROR
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
register_plugin(Formatters.plugin_name, Formatters)
|
|
321
|
-
end
|
|
322
|
-
end
|
data/lib/serega/plugins/if/if.rb
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaPlugins
|
|
5
|
-
#
|
|
6
|
-
# Plugin :if
|
|
7
|
-
#
|
|
8
|
-
# Adds `:if`, `:unless`, `:if_value`, `:unless_value` attribute options to
|
|
9
|
-
# conditionally remove attributes from the response.
|
|
10
|
-
#
|
|
11
|
-
# `:if`/`:unless` receive the serialized object and context, and are
|
|
12
|
-
# checked before the attribute value is found. `:if_value`/`:unless_value`
|
|
13
|
-
# receive the already-found value and context, checked after. The latter
|
|
14
|
-
# two cannot be used with the `:serializer` option, since a relationship
|
|
15
|
-
# has no "serialized value" of its own — use `:if`/`:unless` instead.
|
|
16
|
-
#
|
|
17
|
-
# See also the plugin-free `:hide` option (README.md#selecting-fields),
|
|
18
|
-
# which hides an attribute unconditionally.
|
|
19
|
-
#
|
|
20
|
-
# @example
|
|
21
|
-
# class UserSerializer < Serega
|
|
22
|
-
# attribute :email, if: :active? # if user.active?
|
|
23
|
-
# attribute :email, if: proc { |user, ctx| user == ctx[:current_user] } # using context
|
|
24
|
-
# attribute :email, if: CustomPolicy.method(:view_email?) # any callable
|
|
25
|
-
#
|
|
26
|
-
# attribute :email, unless: :hidden? # unless user.hidden?
|
|
27
|
-
# attribute :email, if_value: :present? # if email.present?
|
|
28
|
-
# attribute :email, unless_value: :blank? # unless email.blank?
|
|
29
|
-
# end
|
|
30
|
-
#
|
|
31
|
-
module If
|
|
32
|
-
# @return [Symbol] Plugin name
|
|
33
|
-
# @private
|
|
34
|
-
def self.plugin_name
|
|
35
|
-
:if
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
# Applies plugin code to specific serializer
|
|
40
|
-
#
|
|
41
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
42
|
-
# @param _opts [Hash] Plugin options
|
|
43
|
-
#
|
|
44
|
-
# @return [void]
|
|
45
|
-
#
|
|
46
|
-
# @private
|
|
47
|
-
def self.load_plugin(serializer_class, **_opts)
|
|
48
|
-
require_relative "validations/check_opt_if"
|
|
49
|
-
require_relative "validations/check_opt_if_value"
|
|
50
|
-
require_relative "validations/check_opt_unless"
|
|
51
|
-
require_relative "validations/check_opt_unless_value"
|
|
52
|
-
|
|
53
|
-
serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
|
|
54
|
-
serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
|
|
55
|
-
serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
|
|
56
|
-
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
|
57
|
-
serializer_class::SeregaObjectSerializer.include(ObjectSerializerInstanceMethods)
|
|
58
|
-
serializer_class::SeregaDataBuilder.extend(DataBuilderClassMethods)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
# Adds config options and runs other callbacks after plugin was loaded
|
|
63
|
-
#
|
|
64
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
65
|
-
# @param opts [Hash] Plugin options
|
|
66
|
-
#
|
|
67
|
-
# @return [void]
|
|
68
|
-
#
|
|
69
|
-
# @private
|
|
70
|
-
def self.after_load_plugin(serializer_class, **opts)
|
|
71
|
-
serializer_class.config.attribute_keys << :if << :if_value << :unless << :unless_value
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
#
|
|
75
|
-
# SeregaAttributeNormalizer additional/patched instance methods
|
|
76
|
-
#
|
|
77
|
-
# @see SeregaAttributeNormalizer::AttributeInstanceMethods
|
|
78
|
-
#
|
|
79
|
-
# @private
|
|
80
|
-
module AttributeNormalizerInstanceMethods
|
|
81
|
-
#
|
|
82
|
-
# Returns prepared attribute :if_options.
|
|
83
|
-
#
|
|
84
|
-
# @return [Hash] prepared options for :if plugin
|
|
85
|
-
#
|
|
86
|
-
def if_options
|
|
87
|
-
@if_options ||= {
|
|
88
|
-
if: prepare_if_option(init_opts[:if]),
|
|
89
|
-
unless: prepare_if_option(init_opts[:unless]),
|
|
90
|
-
if_value: prepare_if_option(init_opts[:if_value]),
|
|
91
|
-
unless_value: prepare_if_option(init_opts[:unless_value])
|
|
92
|
-
}.freeze
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
#
|
|
96
|
-
# Returns method signatures for all if options for optimized calling
|
|
97
|
-
#
|
|
98
|
-
# @return [Hash] Hash with signatures for each if option type
|
|
99
|
-
#
|
|
100
|
-
def if_options_signatures
|
|
101
|
-
@if_options_signatures ||= {
|
|
102
|
-
if: if_option_signature(:if),
|
|
103
|
-
unless: if_option_signature(:unless),
|
|
104
|
-
if_value: if_option_signature(:if_value),
|
|
105
|
-
unless_value: if_option_signature(:unless_value)
|
|
106
|
-
}.freeze
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
private
|
|
110
|
-
|
|
111
|
-
def if_option_signature(option_name)
|
|
112
|
-
callable = if_options[option_name]
|
|
113
|
-
return unless callable
|
|
114
|
-
|
|
115
|
-
SeregaUtils::MethodSignature.call(callable, pos_limit: 2, keyword_args: [:ctx])
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def prepare_if_option(if_option)
|
|
119
|
-
return unless if_option
|
|
120
|
-
return KeywordConditionResolver.new(if_option) if if_option.is_a?(Symbol)
|
|
121
|
-
|
|
122
|
-
if_option
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
#
|
|
127
|
-
# Resolves keyword-based conditions for if/unless options
|
|
128
|
-
#
|
|
129
|
-
# @private
|
|
130
|
-
class KeywordConditionResolver
|
|
131
|
-
def initialize(keyword)
|
|
132
|
-
@keyword = keyword
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
#
|
|
136
|
-
# Calls the keyword method on the object
|
|
137
|
-
#
|
|
138
|
-
# @param object [Object] the object to call method on
|
|
139
|
-
# @return [Object] result of method call
|
|
140
|
-
#
|
|
141
|
-
def call(object)
|
|
142
|
-
object.public_send(@keyword)
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
#
|
|
147
|
-
# SeregaAttribute additional/patched instance methods
|
|
148
|
-
#
|
|
149
|
-
# @see Serega::SeregaAttribute
|
|
150
|
-
#
|
|
151
|
-
# @private
|
|
152
|
-
module AttributeInstanceMethods
|
|
153
|
-
# @return [Hash] provided :if options
|
|
154
|
-
attr_reader :opt_if
|
|
155
|
-
|
|
156
|
-
# @return [Hash] signatures for provided :if options
|
|
157
|
-
attr_reader :opt_if_signatures
|
|
158
|
-
|
|
159
|
-
private
|
|
160
|
-
|
|
161
|
-
def set_normalized_vars(normalizer)
|
|
162
|
-
super
|
|
163
|
-
@opt_if = normalizer.if_options
|
|
164
|
-
@opt_if_signatures = normalizer.if_options_signatures
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
#
|
|
169
|
-
# Serega::SeregaPlanPoint additional/patched instance methods
|
|
170
|
-
#
|
|
171
|
-
# @see Serega::SeregaPlanPoint::InstanceMethods
|
|
172
|
-
#
|
|
173
|
-
# @private
|
|
174
|
-
module PlanPointInstanceMethods
|
|
175
|
-
#
|
|
176
|
-
# @return [Boolean] Should we show attribute or not
|
|
177
|
-
# Conditions for this checks are specified by :if and :unless attribute options.
|
|
178
|
-
#
|
|
179
|
-
def satisfy_if_conditions?(obj, ctx)
|
|
180
|
-
check_if_unless(obj, ctx, :if, :unless)
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
#
|
|
184
|
-
# @return [Boolean] Should we show attribute with specific value or not.
|
|
185
|
-
# Conditions for this checks are specified by :if_value and :unless_value attribute options.
|
|
186
|
-
#
|
|
187
|
-
def satisfy_if_value_conditions?(value, ctx)
|
|
188
|
-
check_if_unless(value, ctx, :if_value, :unless_value)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
private
|
|
192
|
-
|
|
193
|
-
def check_if_unless(obj, ctx, opt_if_name, opt_unless_name)
|
|
194
|
-
opt_if = attribute.opt_if[opt_if_name]
|
|
195
|
-
opt_unless = attribute.opt_if[opt_unless_name]
|
|
196
|
-
return true if opt_if.nil? && opt_unless.nil?
|
|
197
|
-
|
|
198
|
-
res_if = opt_if ? check_condition(opt_if, opt_if_name, obj, ctx) : true
|
|
199
|
-
res_unless = opt_unless ? !check_condition(opt_unless, opt_unless_name, obj, ctx) : true
|
|
200
|
-
res_if && res_unless
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def check_condition(condition, condition_name, object, context)
|
|
204
|
-
signature = attribute.opt_if_signatures[condition_name]
|
|
205
|
-
|
|
206
|
-
case signature
|
|
207
|
-
when "1" then condition.call(object)
|
|
208
|
-
when "2" then condition.call(object, context)
|
|
209
|
-
when "1_ctx" then condition.call(object, ctx: context)
|
|
210
|
-
when "2_ctx" then condition.call(object, context, ctx: context)
|
|
211
|
-
else # "0"
|
|
212
|
-
condition.call
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
#
|
|
218
|
-
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
|
219
|
-
#
|
|
220
|
-
# @see Serega::SeregaValidations::CheckAttributeParams
|
|
221
|
-
#
|
|
222
|
-
# @private
|
|
223
|
-
module CheckAttributeParamsInstanceMethods
|
|
224
|
-
private
|
|
225
|
-
|
|
226
|
-
def check_opts
|
|
227
|
-
super
|
|
228
|
-
|
|
229
|
-
CheckOptIf.call(opts)
|
|
230
|
-
CheckOptUnless.call(opts)
|
|
231
|
-
CheckOptIfValue.call(opts)
|
|
232
|
-
CheckOptUnlessValue.call(opts)
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
#
|
|
237
|
-
# SeregaDataBuilder additional/patched class methods
|
|
238
|
-
#
|
|
239
|
-
# Overrides `build_data_object` to handle plans where some keys were skipped
|
|
240
|
-
# by `:if`/`:unless`/`:if_value`/`:unless_value` conditions, so the Data class
|
|
241
|
-
# is built from the actually-present keys rather than the full plan.
|
|
242
|
-
#
|
|
243
|
-
# @see Serega::SeregaDataBuilder
|
|
244
|
-
#
|
|
245
|
-
# @private
|
|
246
|
-
module DataBuilderClassMethods
|
|
247
|
-
private
|
|
248
|
-
|
|
249
|
-
def build_data_object(plan, hash_data)
|
|
250
|
-
if hash_data.size < plan.points.size
|
|
251
|
-
plan.class.data_class_for(hash_data.keys).new(**hash_data)
|
|
252
|
-
else
|
|
253
|
-
super
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
#
|
|
259
|
-
# SeregaObjectSerializer additional/patched class methods
|
|
260
|
-
#
|
|
261
|
-
# @see Serega::SeregaObjectSerializer
|
|
262
|
-
#
|
|
263
|
-
# @private
|
|
264
|
-
module ObjectSerializerInstanceMethods
|
|
265
|
-
private
|
|
266
|
-
|
|
267
|
-
# Skip the whole point for this object (its value is never resolved).
|
|
268
|
-
def resolve_point(object, point, _container, _batches, _child_plan)
|
|
269
|
-
return unless point.satisfy_if_conditions?(object, context)
|
|
270
|
-
|
|
271
|
-
super
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
# Skip only the assignment based on the resolved value. Levels resolve
|
|
275
|
-
# points in attribute order, so simply not assigning keeps the remaining
|
|
276
|
-
# keys in declared order — no slot to reserve or delete.
|
|
277
|
-
def write_value(value, point, _container)
|
|
278
|
-
return unless point.satisfy_if_value_conditions?(value, context)
|
|
279
|
-
|
|
280
|
-
super
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
register_plugin(If.plugin_name, If)
|
|
286
|
-
end
|
|
287
|
-
end
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Serega
|
|
4
|
-
module SeregaPlugins
|
|
5
|
-
module If
|
|
6
|
-
#
|
|
7
|
-
# Validator for attribute :if option
|
|
8
|
-
#
|
|
9
|
-
# @private
|
|
10
|
-
class CheckOptIf
|
|
11
|
-
class << self
|
|
12
|
-
#
|
|
13
|
-
# Checks attribute :if option that must be [nil, Symbol, Proc, #call]
|
|
14
|
-
#
|
|
15
|
-
# @param opts [Hash] Attribute options
|
|
16
|
-
#
|
|
17
|
-
# @raise [SeregaError] Attribute validation error
|
|
18
|
-
#
|
|
19
|
-
# @return [void]
|
|
20
|
-
#
|
|
21
|
-
def call(opts)
|
|
22
|
-
return unless opts.key?(:if)
|
|
23
|
-
|
|
24
|
-
check_type(opts[:if])
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def check_type(value)
|
|
30
|
-
return if value.is_a?(Symbol)
|
|
31
|
-
raise SeregaError, must_be_callable unless value.respond_to?(:call)
|
|
32
|
-
|
|
33
|
-
signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: [:ctx])
|
|
34
|
-
raise SeregaError, signature_error unless valid_signature?(signature)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def must_be_callable
|
|
38
|
-
"Invalid attribute option :if. It must be a Symbol, a Proc or respond to :call"
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def valid_signature?(signature)
|
|
42
|
-
case signature
|
|
43
|
-
when "0" # no parameters
|
|
44
|
-
true
|
|
45
|
-
when "1" # (object)
|
|
46
|
-
true
|
|
47
|
-
when "2" # (object, context)
|
|
48
|
-
true
|
|
49
|
-
when "1_ctx" # (object, :ctx)
|
|
50
|
-
true
|
|
51
|
-
when "2_ctx" # (object, context, :ctx)
|
|
52
|
-
true
|
|
53
|
-
else
|
|
54
|
-
false
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def signature_error
|
|
59
|
-
<<~ERROR.strip
|
|
60
|
-
Invalid attribute option :if parameters, valid parameters signatures:
|
|
61
|
-
- () # no parameters
|
|
62
|
-
- (object) # one positional parameter
|
|
63
|
-
- (object, context) # two positional parameters
|
|
64
|
-
- (object, :ctx) # one positional parameter and :ctx keyword
|
|
65
|
-
- (object, context, :ctx) # two positional parameters and :ctx keyword
|
|
66
|
-
ERROR
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|