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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +124 -1207
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +186 -131
  5. data/lib/serega/attribute_normalizer.rb +164 -345
  6. data/lib/serega/attribute_normalizers/batch.rb +45 -0
  7. data/lib/serega/attribute_normalizers/block.rb +81 -0
  8. data/lib/serega/attribute_normalizers/delegate.rb +34 -0
  9. data/lib/serega/attribute_normalizers/hash_access.rb +37 -0
  10. data/lib/serega/attribute_normalizers/preloads.rb +46 -0
  11. data/lib/serega/attribute_value_resolvers/batch.rb +5 -54
  12. data/lib/serega/attribute_value_resolvers/const.rb +2 -24
  13. data/lib/serega/attribute_value_resolvers/delegate.rb +17 -28
  14. data/lib/serega/attribute_value_resolvers/hash_access.rb +34 -52
  15. data/lib/serega/attribute_value_resolvers/keyword.rb +10 -25
  16. data/lib/serega/attribute_values.rb +46 -0
  17. data/lib/serega/batch_loader.rb +37 -0
  18. data/lib/serega/conditions.rb +56 -0
  19. data/lib/serega/config.rb +105 -144
  20. data/lib/serega/engine/pulls.rb +64 -0
  21. data/lib/serega/engine/run.rb +106 -0
  22. data/lib/serega/errors.rb +36 -8
  23. data/lib/serega/formatter.rb +98 -0
  24. data/lib/serega/plan/cache.rb +96 -0
  25. data/lib/serega/plan/per_ractor_cache.rb +23 -0
  26. data/lib/serega/plan.rb +101 -128
  27. data/lib/serega/plan_point.rb +44 -151
  28. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +13 -80
  29. data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +8 -75
  30. data/lib/serega/plugins/context_metadata/context_metadata.rb +40 -123
  31. data/lib/serega/plugins/metadata/check_params.rb +80 -0
  32. data/lib/serega/plugins/metadata/meta_attribute.rb +40 -79
  33. data/lib/serega/plugins/metadata/metadata.rb +40 -168
  34. data/lib/serega/plugins/root/root.rb +91 -235
  35. data/lib/serega/plugins.rb +16 -49
  36. data/lib/serega/presenter.rb +61 -19
  37. data/lib/serega/result_builder.rb +68 -0
  38. data/lib/serega/result_code.rb +203 -0
  39. data/lib/serega/shareable.rb +52 -0
  40. data/lib/serega/source_group.rb +187 -0
  41. data/lib/serega/utils/collection_detector.rb +19 -13
  42. data/lib/serega/utils/deep_merge.rb +26 -0
  43. data/lib/serega/utils/enum_deep_dup.rb +10 -16
  44. data/lib/serega/utils/enum_deep_freeze.rb +8 -14
  45. data/lib/serega/utils/method_signature.rb +6 -15
  46. data/lib/serega/utils/parse_string_modifiers.rb +63 -0
  47. data/lib/serega/utils/ractor_local.rb +36 -0
  48. data/lib/serega/utils/serialized_attribute_error.rb +60 -20
  49. data/lib/serega/utils/to_hash.rb +5 -26
  50. data/lib/serega/validations/attribute/check_block.rb +3 -15
  51. data/lib/serega/validations/attribute/check_name.rb +4 -15
  52. data/lib/serega/validations/attribute/check_opt_base_serializer.rb +3 -15
  53. data/lib/serega/validations/attribute/check_opt_batch.rb +26 -56
  54. data/lib/serega/validations/attribute/check_opt_condition.rb +55 -0
  55. data/lib/serega/validations/attribute/check_opt_delegate.rb +10 -63
  56. data/lib/serega/validations/attribute/check_opt_format.rb +36 -0
  57. data/lib/serega/validations/attribute/check_opt_hash_access.rb +11 -59
  58. data/lib/serega/validations/attribute/check_opt_hide.rb +3 -14
  59. data/lib/serega/validations/attribute/check_opt_many.rb +18 -20
  60. data/lib/serega/validations/attribute/check_opt_method.rb +3 -25
  61. data/lib/serega/validations/attribute/check_opt_preload.rb +3 -26
  62. data/lib/serega/validations/attribute/check_opt_serializer.rb +4 -18
  63. data/lib/serega/validations/attribute/check_opt_value.rb +3 -21
  64. data/lib/serega/validations/check_attribute_params.rb +92 -71
  65. data/lib/serega/validations/check_batch_loader_params.rb +43 -62
  66. data/lib/serega/validations/check_formatter.rb +80 -0
  67. data/lib/serega/validations/check_serialize_keywords.rb +23 -0
  68. data/lib/serega/validations/initiate/check_modifiers.rb +14 -20
  69. data/lib/serega/validations/utils/check_allowed_keys.rb +3 -13
  70. data/lib/serega/validations/utils/check_hash_access_value.rb +44 -0
  71. data/lib/serega/validations/utils/check_opt_is_bool.rb +3 -13
  72. data/lib/serega/validations/utils/check_opt_is_hash.rb +3 -13
  73. data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +3 -13
  74. data/lib/serega/version.rb +1 -4
  75. data/lib/serega.rb +459 -388
  76. data/sig/serega/plugins.rbs +49 -0
  77. data/sig/serega.rbs +142 -0
  78. metadata +42 -38
  79. data/lib/serega/data_builder.rb +0 -54
  80. data/lib/serega/engine/level.rb +0 -59
  81. data/lib/serega/engine/level_queue.rb +0 -56
  82. data/lib/serega/engine/loader.rb +0 -70
  83. data/lib/serega/helpers/serializer_class_helper.rb +0 -19
  84. data/lib/serega/object_serializer.rb +0 -125
  85. data/lib/serega/plugins/camel_case/camel_case.rb +0 -213
  86. data/lib/serega/plugins/depth_limit/depth_limit.rb +0 -206
  87. data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +0 -69
  88. data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +0 -38
  89. data/lib/serega/plugins/formatters/formatters.rb +0 -322
  90. data/lib/serega/plugins/if/if.rb +0 -287
  91. data/lib/serega/plugins/if/validations/check_opt_if.rb +0 -72
  92. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +0 -77
  93. data/lib/serega/plugins/if/validations/check_opt_unless.rb +0 -72
  94. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +0 -77
  95. data/lib/serega/plugins/metadata/validations/check_block.rb +0 -47
  96. data/lib/serega/plugins/metadata/validations/check_opt_const.rb +0 -39
  97. data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +0 -35
  98. data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +0 -35
  99. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +0 -61
  100. data/lib/serega/plugins/metadata/validations/check_opts.rb +0 -59
  101. data/lib/serega/plugins/metadata/validations/check_path.rb +0 -50
  102. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +0 -111
  103. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +0 -63
  104. data/lib/serega/utils/symbol_name.rb +0 -39
  105. data/lib/serega/validations/attribute/check_opt_const.rb +0 -40
  106. data/lib/serega/validations/check_initiate_params.rb +0 -57
  107. 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
@@ -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