serega 0.20.1 → 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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -226
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +31 -3
  5. data/lib/serega/attribute_normalizer.rb +143 -42
  6. data/lib/serega/batch/attribute_loader.rb +70 -0
  7. data/lib/serega/batch/attribute_loaders.rb +59 -0
  8. data/lib/serega/batch/auto_resolver.rb +24 -0
  9. data/lib/serega/batch/auto_resolver_factory.rb +48 -0
  10. data/lib/serega/batch/loader.rb +67 -0
  11. data/lib/serega/config.rb +59 -1
  12. data/lib/serega/object_serializer.rb +11 -9
  13. data/lib/serega/plan.rb +16 -0
  14. data/lib/serega/plan_point.rb +31 -5
  15. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +42 -23
  16. data/lib/serega/plugins/activerecord_preloads/lib/active_record_objects.rb +31 -0
  17. data/lib/serega/plugins/camel_case/camel_case.rb +1 -1
  18. data/lib/serega/plugins/formatters/formatters.rb +79 -22
  19. data/lib/serega/plugins/if/if.rb +41 -22
  20. data/lib/serega/plugins/if/validations/check_opt_if.rb +27 -6
  21. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +27 -6
  22. data/lib/serega/plugins/if/validations/check_opt_unless.rb +27 -6
  23. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +27 -6
  24. data/lib/serega/plugins/metadata/meta_attribute.rb +9 -10
  25. data/lib/serega/plugins/metadata/validations/check_block.rb +2 -4
  26. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +2 -3
  27. data/lib/serega/plugins/presenter/presenter.rb +1 -1
  28. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +43 -30
  29. data/lib/serega/utils/format_user_preloads.rb +58 -0
  30. data/lib/serega/utils/method_signature.rb +89 -0
  31. data/lib/serega/utils/preload_paths.rb +53 -0
  32. data/lib/serega/utils/preloads_constructor.rb +77 -0
  33. data/lib/serega/validations/attribute/check_block.rb +38 -6
  34. data/lib/serega/validations/attribute/check_opt_batch.rb +101 -0
  35. data/lib/serega/validations/attribute/check_opt_const.rb +1 -0
  36. data/lib/serega/validations/attribute/check_opt_delegate.rb +1 -0
  37. data/lib/serega/validations/attribute/check_opt_method.rb +1 -0
  38. data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload.rb +2 -2
  39. data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload_path.rb +3 -3
  40. data/lib/serega/validations/attribute/check_opt_value.rb +35 -9
  41. data/lib/serega/validations/check_attribute_params.rb +3 -2
  42. data/lib/serega/validations/check_batch_loader_params.rb +80 -0
  43. data/lib/serega/validations/initiate/check_modifiers.rb +1 -1
  44. data/lib/serega.rb +110 -11
  45. metadata +17 -37
  46. data/lib/serega/plugins/batch/batch.rb +0 -168
  47. data/lib/serega/plugins/batch/lib/batch_config.rb +0 -77
  48. data/lib/serega/plugins/batch/lib/loader.rb +0 -113
  49. data/lib/serega/plugins/batch/lib/loaders.rb +0 -45
  50. data/lib/serega/plugins/batch/lib/modules/attribute.rb +0 -26
  51. data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +0 -78
  52. data/lib/serega/plugins/batch/lib/modules/check_attribute_params.rb +0 -22
  53. data/lib/serega/plugins/batch/lib/modules/config.rb +0 -23
  54. data/lib/serega/plugins/batch/lib/modules/object_serializer.rb +0 -46
  55. data/lib/serega/plugins/batch/lib/modules/plan_point.rb +0 -22
  56. data/lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb +0 -43
  57. data/lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb +0 -54
  58. data/lib/serega/plugins/batch/lib/plugins_extensions/if.rb +0 -31
  59. data/lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb +0 -34
  60. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb +0 -43
  61. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +0 -59
  62. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +0 -62
  63. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +0 -60
  64. data/lib/serega/plugins/preloads/lib/modules/attribute.rb +0 -28
  65. data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +0 -99
  66. data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +0 -22
  67. data/lib/serega/plugins/preloads/lib/modules/config.rb +0 -19
  68. data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +0 -41
  69. data/lib/serega/plugins/preloads/lib/preload_paths.rb +0 -53
  70. data/lib/serega/plugins/preloads/lib/preloads_config.rb +0 -62
  71. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +0 -79
  72. data/lib/serega/plugins/preloads/preloads.rb +0 -162
  73. data/lib/serega/utils/params_count.rb +0 -50
  74. data/lib/serega/validations/utils/check_extra_keyword_arg.rb +0 -33
@@ -25,6 +25,14 @@ class Serega
25
25
  # @return [Hash] Attributes to serialize
26
26
  attr_reader :modifiers
27
27
 
28
+ # Shows preloads for nested attributes
29
+ # @return [Hash] preloads for nested attributes
30
+ attr_reader :preloads
31
+
32
+ # Shows preloads_path for current attribute
33
+ # @return [Array<Symbol>, nil] preloads path for current attribute
34
+ attr_reader :preloads_path
35
+
28
36
  #
29
37
  # Initializes plan point
30
38
  #
@@ -46,8 +54,8 @@ class Serega
46
54
 
47
55
  # Attribute `value`
48
56
  # @see SeregaAttribute::AttributeInstanceMethods#value
49
- def value(obj, ctx)
50
- attribute.value(obj, ctx)
57
+ def value(obj, ctx, batches: nil)
58
+ attribute.value(obj, ctx, batches: batches)
51
59
  end
52
60
 
53
61
  # Attribute `name`
@@ -68,6 +76,16 @@ class Serega
68
76
  attribute.serializer
69
77
  end
70
78
 
79
+ def batch?
80
+ !attribute.batch_loaders.empty?
81
+ end
82
+
83
+ # Attribute `batch_loaders`
84
+ # @see SeregaAttribute::AttributeInstanceMethods#batch_loaders
85
+ def batch_loaders
86
+ attribute.batch_loaders
87
+ end
88
+
71
89
  #
72
90
  # @return [SeregaObjectSerializer] object serializer for child plan
73
91
  #
@@ -77,11 +95,11 @@ class Serega
77
95
 
78
96
  private
79
97
 
80
- # Patched in:
81
- # - plugin :batch (prepares @batch)
82
- # - plugin :preloads (prepares @preloads and @preloads_path)
83
98
  def set_normalized_vars
84
99
  @child_plan = prepare_child_plan
100
+ @preloads = prepare_preloads
101
+ @preloads_path = prepare_preloads_path
102
+ plan.mark_as_has_batch_points if batch?
85
103
  end
86
104
 
87
105
  def prepare_child_plan
@@ -91,6 +109,14 @@ class Serega
91
109
 
92
110
  serializer::SeregaPlan.new(self, fields)
93
111
  end
112
+
113
+ def prepare_preloads
114
+ SeregaUtils::PreloadsConstructor.call(child_plan)
115
+ end
116
+
117
+ def prepare_preloads_path
118
+ attribute.preloads_path
119
+ end
94
120
  end
95
121
 
96
122
  extend SeregaHelpers::SerializerClassHelper
@@ -4,7 +4,6 @@ class Serega
4
4
  module SeregaPlugins
5
5
  #
6
6
  # Plugin :activerecord_preloads
7
- # (depends on :preloads plugin, that must be loaded first)
8
7
  #
9
8
  # Automatically preloads associations to serialized objects
10
9
  #
@@ -14,10 +13,9 @@ class Serega
14
13
  #
15
14
  # @example
16
15
  # class AppSerializer < Serega
17
- # plugin :preloads,
18
- # auto_preload_attributes_with_delegate: true,
19
- # auto_preload_attributes_with_serializer: true,
20
- # auto_hide_attributes_with_preload: true
16
+ # config.auto_preload_attributes_with_delegate = true
17
+ # config.auto_preload_attributes_with_serializer = true
18
+ # config.auto_hide_attributes_with_preload = true
21
19
  #
22
20
  # plugin :activerecord_preloads
23
21
  # end
@@ -62,14 +60,6 @@ class Serega
62
60
  opts.each_key do |key|
63
61
  raise SeregaError, "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. No options are allowed"
64
62
  end
65
-
66
- unless serializer_class.plugin_used?(:preloads)
67
- raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded after the :preloads plugin. Please load the :preloads plugin first"
68
- end
69
-
70
- if serializer_class.plugin_used?(:batch)
71
- raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
72
- end
73
63
  end
74
64
 
75
65
  #
@@ -82,14 +72,52 @@ class Serega
82
72
  #
83
73
  def self.load_plugin(serializer_class, **_opts)
84
74
  require_relative "lib/preloader"
75
+ require_relative "lib/active_record_objects"
85
76
 
86
77
  serializer_class.include(InstanceMethods)
78
+ serializer_class::SeregaBatchAttributeLoader.include(BatchAttributeLoaderInstanceMethods)
79
+ end
80
+
81
+ #
82
+ # Overrides SeregaBatch::AttributeLoader class instance methods
83
+ #
84
+ module BatchAttributeLoaderInstanceMethods
85
+ private
86
+
87
+ # Preloads associations to batch-generated data
88
+ def load_one(serializer_class, batch_loader_name, context)
89
+ batch_loaded_data = super
90
+
91
+ preloads = point.preloads
92
+ return batch_loaded_data if preloads.empty?
93
+
94
+ ar_objects = ActiveRecordObjects.call(batch_loaded_data)
95
+ Preloader.preload(ar_objects, preloads)
96
+
97
+ batch_loaded_data
98
+ end
87
99
  end
88
100
 
89
101
  #
90
102
  # Overrides Serega class instance methods
91
103
  #
92
104
  module InstanceMethods
105
+ #
106
+ # Preloads associations to object
107
+ #
108
+ # @param object [Object] Any object
109
+ # @return provided object
110
+ #
111
+ def preload_associations_to(object)
112
+ return object if object.nil? || (object.is_a?(Array) && object.empty?)
113
+
114
+ preloads = preloads() # `preloads()` method comes from :preloads plugin
115
+ return object if preloads.empty?
116
+
117
+ Preloader.preload(object, preloads)
118
+ object
119
+ end
120
+
93
121
  private
94
122
 
95
123
  #
@@ -97,18 +125,9 @@ class Serega
97
125
  # Preloads associations to object before serialization
98
126
  #
99
127
  def serialize(object, _opts)
100
- object = add_preloads(object)
128
+ preload_associations_to(object)
101
129
  super
102
130
  end
103
-
104
- def add_preloads(obj)
105
- return obj if obj.nil? || (obj.is_a?(Array) && obj.empty?)
106
-
107
- preloads = preloads() # `preloads()` method comes from :preloads plugin
108
- return obj if preloads.empty?
109
-
110
- Preloader.preload(obj, preloads)
111
- end
112
131
  end
113
132
  end
114
133
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module ActiverecordPreloads
6
+ # Detects and returns found ActiveRecord objects
7
+ class ActiveRecordObjects
8
+ class << self
9
+ # Iterates over provided data and selects ActiveRecord objects
10
+ # @param data [Hash,Array,Object] any data
11
+ # @return [Array] Found ActiveRecord objects
12
+ def call(data)
13
+ res = []
14
+ extract(data, res)
15
+ res
16
+ end
17
+
18
+ private
19
+
20
+ def extract(data, res)
21
+ case data
22
+ when ActiveRecord::Base then res << data
23
+ when Array then data.each { |value| extract(value, res) }
24
+ when Hash then data.each_value { |value| extract(value, res) }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -203,7 +203,7 @@ class Serega
203
203
  res = super
204
204
  return res if init_opts[:camel_case] == false
205
205
 
206
- self.class.serializer_class.config.camel_case.transform.call(res.to_s).to_sym
206
+ self.class.serializer_class.config.camel_case.transform.call(+res.to_s).to_sym
207
207
  end
208
208
  end
209
209
  end
@@ -64,10 +64,6 @@ class Serega
64
64
  "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
65
65
  " - :formatters [Hash<Symbol, #call>] - Formatters (names and according callable values)"
66
66
  end
67
-
68
- if serializer_class.plugin_used?(:batch)
69
- raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
70
- end
71
67
  end
72
68
 
73
69
  #
@@ -81,6 +77,7 @@ class Serega
81
77
  def self.load_plugin(serializer_class, **_opts)
82
78
  serializer_class::SeregaConfig.include(ConfigInstanceMethods)
83
79
  serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
80
+ serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
84
81
  serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
85
82
  end
86
83
 
@@ -155,7 +152,7 @@ class Serega
155
152
  end
156
153
 
157
154
  #
158
- # Attribute class additional/patched instance methods
155
+ # AttributeNormalizer class additional/patched instance methods
159
156
  #
160
157
  # @see SeregaAttributeNormalizer
161
158
  #
@@ -163,29 +160,69 @@ class Serega
163
160
  # Block or callable instance that will format attribute values
164
161
  # @return [Proc, #call, nil] Block or callable instance that will format attribute values
165
162
  def formatter
166
- return @formatter if instance_variable_defined?(:@formatter)
163
+ @formatter ||= prepare_formatter
164
+ end
167
165
 
168
- @formatter = prepare_formatter
166
+ #
167
+ # Returns formatter method signature for optimized calling
168
+ #
169
+ # @return [String, nil] Formatter signature ("1", "2", "1_ctx") or nil if no formatter
170
+ #
171
+ def formatter_signature
172
+ @formatter_signature ||= prepare_formatter_signature
169
173
  end
170
174
 
171
175
  private
172
176
 
173
- def prepare_value_block
174
- return super unless formatter
175
-
176
- # Wrap original block into formatter block
177
- proc do |object, context|
178
- value = super.call(object, context)
179
- formatter.call(value, context)
180
- end
181
- end
182
-
183
177
  def prepare_formatter
184
178
  formatter = init_opts[:format]
185
179
  return unless formatter
186
180
 
187
181
  formatter = self.class.serializer_class.config.formatters.opts.fetch(formatter) if formatter.is_a?(Symbol)
188
- prepare_callable_proc(formatter)
182
+ formatter
183
+ end
184
+
185
+ def prepare_formatter_signature
186
+ return unless formatter
187
+
188
+ SeregaUtils::MethodSignature.call(formatter, pos_limit: 2, keyword_args: [:ctx])
189
+ end
190
+ end
191
+
192
+ #
193
+ # Attribute class additional/patched instance methods
194
+ #
195
+ # @see SeregaAttribute
196
+ #
197
+ module AttributeInstanceMethods
198
+ #
199
+ # Returns formatted attribute value
200
+ #
201
+ # @param object [Object] Serialized object
202
+ # @param context [Hash] Serialization context
203
+ #
204
+ # @return [Object] Formatted attribute value
205
+ #
206
+ def value(object, context, batches: nil)
207
+ result = super # `:batches` parameter is needed to find unformatted result
208
+ return result unless formatter
209
+
210
+ case formatter_signature
211
+ when "1" then formatter.call(result)
212
+ when "1_ctx" then formatter.call(result, ctx: context)
213
+ else # "2"
214
+ formatter.call(result, context)
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ attr_reader :formatter, :formatter_signature
221
+
222
+ def set_normalized_vars(normalizer)
223
+ super
224
+ @formatter = normalizer.formatter
225
+ @formatter_signature = normalizer.formatter_signature
189
226
  end
190
227
  end
191
228
 
@@ -241,13 +278,33 @@ class Serega
241
278
  def call(formatter_name, formatter)
242
279
  raise Serega::SeregaError, "Option #{formatter_name.inspect} must have callable value" unless formatter.respond_to?(:call)
243
280
 
244
- SeregaValidations::Utils::CheckExtraKeywordArg.call(formatter, "#{formatter_name.inspect} value")
245
- params_count = SeregaUtils::ParamsCount.call(formatter, max_count: 2)
281
+ signature = SeregaUtils::MethodSignature.call(formatter, pos_limit: 2, keyword_args: [:ctx])
282
+ raise SeregaError, signature_error unless valid_signature?(signature)
283
+ end
284
+
285
+ private
246
286
 
247
- if params_count > 2
248
- raise SeregaError, "Formatter can have maximum 2 parameters (value to format, context)"
287
+ def valid_signature?(signature)
288
+ case signature
289
+ when "1" # (object)
290
+ true
291
+ when "2" # (object, context)
292
+ true
293
+ when "1_ctx" # (object, :ctx)
294
+ true
295
+ else
296
+ false
249
297
  end
250
298
  end
299
+
300
+ def signature_error
301
+ <<~ERROR.strip
302
+ Invalid formatter parameters, valid parameters signatures:
303
+ - (object) # one positional parameter
304
+ - (object, context) # two positional parameters
305
+ - (object, :ctx) # one positional parameter and :ctx keyword
306
+ ERROR
307
+ end
251
308
  end
252
309
  end
253
310
  end
@@ -50,19 +50,6 @@ class Serega
50
50
  :if
51
51
  end
52
52
 
53
- # Checks requirements to load plugin
54
- #
55
- # @param serializer_class [Class<Serega>] Current serializer class
56
- # @param opts [Hash] plugin options
57
- #
58
- # @return [void]
59
- #
60
- def self.before_load_plugin(serializer_class, **opts)
61
- if serializer_class.plugin_used?(:batch)
62
- raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
63
- end
64
- end
65
-
66
53
  #
67
54
  # Applies plugin code to specific serializer
68
55
  #
@@ -116,18 +103,34 @@ class Serega
116
103
  }.freeze
117
104
  end
118
105
 
106
+ #
107
+ # Returns method signatures for all if options for optimized calling
108
+ #
109
+ # @return [Hash] Hash with signatures for each if option type
110
+ #
111
+ def if_options_signatures
112
+ @if_options_signatures ||= {
113
+ if: if_option_signature(:if),
114
+ unless: if_option_signature(:unless),
115
+ if_value: if_option_signature(:if_value),
116
+ unless_value: if_option_signature(:unless_value)
117
+ }.freeze
118
+ end
119
+
119
120
  private
120
121
 
122
+ def if_option_signature(option_name)
123
+ callable = if_options[option_name]
124
+ return unless callable
125
+
126
+ SeregaUtils::MethodSignature.call(callable, pos_limit: 2, keyword_args: [:ctx])
127
+ end
128
+
121
129
  def prepare_if_option(if_option)
122
130
  return unless if_option
123
131
  return proc { |val| val.public_send(if_option) } if if_option.is_a?(Symbol)
124
132
 
125
- params_count = SeregaUtils::ParamsCount.call(if_option, max_count: 2)
126
- case params_count
127
- when 0 then proc { if_option.call }
128
- when 1 then proc { |obj| if_option.call(obj) }
129
- else if_option
130
- end
133
+ if_option
131
134
  end
132
135
  end
133
136
 
@@ -137,14 +140,18 @@ class Serega
137
140
  # @see Serega::SeregaAttribute
138
141
  #
139
142
  module AttributeInstanceMethods
140
- # @return provided :if options
143
+ # @return [Hash] provided :if options
141
144
  attr_reader :opt_if
142
145
 
146
+ # @return [Hash] signatures for provided :if options
147
+ attr_reader :opt_if_signatures
148
+
143
149
  private
144
150
 
145
151
  def set_normalized_vars(normalizer)
146
152
  super
147
153
  @opt_if = normalizer.if_options
154
+ @opt_if_signatures = normalizer.if_options_signatures
148
155
  end
149
156
  end
150
157
 
@@ -177,10 +184,22 @@ class Serega
177
184
  opt_unless = attribute.opt_if[opt_unless_name]
178
185
  return true if opt_if.nil? && opt_unless.nil?
179
186
 
180
- res_if = opt_if ? opt_if.call(obj, ctx) : true
181
- res_unless = opt_unless ? !opt_unless.call(obj, ctx) : true
187
+ res_if = opt_if ? check_condition(opt_if, opt_if_name, obj, ctx) : true
188
+ res_unless = opt_unless ? !check_condition(opt_unless, opt_unless_name, obj, ctx) : true
182
189
  res_if && res_unless
183
190
  end
191
+
192
+ def check_condition(condition, condition_name, object, context)
193
+ signature = attribute.opt_if_signatures[condition_name]
194
+
195
+ case signature
196
+ when "1" then condition.call(object)
197
+ when "2" then condition.call(object, context)
198
+ when "1_ctx" then condition.call(object, ctx: context)
199
+ else # "0"
200
+ condition.call
201
+ end
202
+ end
184
203
  end
185
204
 
186
205
  #
@@ -29,17 +29,38 @@ class Serega
29
29
  return if value.is_a?(Symbol)
30
30
  raise SeregaError, must_be_callable unless value.respond_to?(:call)
31
31
 
32
- SeregaValidations::Utils::CheckExtraKeywordArg.call(value, ":if option")
33
- params_count = SeregaUtils::ParamsCount.call(value, max_count: 2)
34
-
35
- if params_count > 2
36
- raise SeregaError, "Option :if value should have up to 2 parameters (object, context)"
37
- end
32
+ signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: [:ctx])
33
+ raise SeregaError, signature_error unless valid_signature?(signature)
38
34
  end
39
35
 
40
36
  def must_be_callable
41
37
  "Invalid attribute option :if. It must be a Symbol, a Proc or respond to :call"
42
38
  end
39
+
40
+ def valid_signature?(signature)
41
+ case signature
42
+ when "0" # no parameters
43
+ true
44
+ when "1" # (object)
45
+ true
46
+ when "2" # (object, context)
47
+ true
48
+ when "1_ctx" # (object, :ctx)
49
+ true
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def signature_error
56
+ <<~ERROR.strip
57
+ Invalid attribute option :if parameters, valid parameters signatures:
58
+ - () # no parameters
59
+ - (object) # one positional parameter
60
+ - (object, context) # two positional parameters
61
+ - (object, :ctx) # one positional parameter and :ctx keyword
62
+ ERROR
63
+ end
43
64
  end
44
65
  end
45
66
  end
@@ -34,17 +34,38 @@ class Serega
34
34
  return if value.is_a?(Symbol)
35
35
  raise SeregaError, must_be_callable unless value.respond_to?(:call)
36
36
 
37
- SeregaValidations::Utils::CheckExtraKeywordArg.call(value, ":if_value option")
38
- params_count = SeregaUtils::ParamsCount.call(value, max_count: 2)
39
-
40
- if params_count > 2
41
- raise SeregaError, "Option :if_value value should have up to 2 parameters (value, context)"
42
- end
37
+ signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: [:ctx])
38
+ raise SeregaError, signature_error unless valid_signature?(signature)
43
39
  end
44
40
 
45
41
  def must_be_callable
46
42
  "Invalid attribute option :if_value. It must be a Symbol, a Proc or respond to :call"
47
43
  end
44
+
45
+ def valid_signature?(signature)
46
+ case signature
47
+ when "0" # no parameters
48
+ true
49
+ when "1" # (value)
50
+ true
51
+ when "2" # (value, context)
52
+ true
53
+ when "1_ctx" # (value, :ctx)
54
+ true
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ def signature_error
61
+ <<~ERROR.strip
62
+ Invalid attribute option :if_value parameters, valid parameters signatures:
63
+ - () # no parameters
64
+ - (value) # one positional parameter
65
+ - (value, context) # two positional parameters
66
+ - (value, :ctx) # one positional parameter and :ctx keyword
67
+ ERROR
68
+ end
48
69
  end
49
70
  end
50
71
  end
@@ -29,17 +29,38 @@ class Serega
29
29
  return if value.is_a?(Symbol)
30
30
  raise SeregaError, must_be_callable unless value.respond_to?(:call)
31
31
 
32
- SeregaValidations::Utils::CheckExtraKeywordArg.call(value, ":unless option")
33
- params_count = SeregaUtils::ParamsCount.call(value, max_count: 2)
34
-
35
- if params_count > 2
36
- raise SeregaError, "Option :unless value should have up to 2 parameters (object, context)"
37
- end
32
+ signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: [:ctx])
33
+ raise SeregaError, signature_error unless valid_signature?(signature)
38
34
  end
39
35
 
40
36
  def must_be_callable
41
37
  "Invalid attribute option :unless. It must be a Symbol, a Proc or respond to :call"
42
38
  end
39
+
40
+ def valid_signature?(signature)
41
+ case signature
42
+ when "0" # no parameters
43
+ true
44
+ when "1" # (object)
45
+ true
46
+ when "2" # (object, context)
47
+ true
48
+ when "1_ctx" # (object, :ctx)
49
+ true
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def signature_error
56
+ <<~ERROR.strip
57
+ Invalid attribute option :unless parameters, valid parameters signatures:
58
+ - () # no parameters
59
+ - (object) # one positional parameter
60
+ - (object, context) # two positional parameters
61
+ - (object, :ctx) # one positional parameter and :ctx keyword
62
+ ERROR
63
+ end
43
64
  end
44
65
  end
45
66
  end
@@ -34,17 +34,38 @@ class Serega
34
34
  return if value.is_a?(Symbol)
35
35
  raise SeregaError, must_be_callable unless value.respond_to?(:call)
36
36
 
37
- SeregaValidations::Utils::CheckExtraKeywordArg.call(value, ":unless_value option")
38
- params_count = SeregaUtils::ParamsCount.call(value, max_count: 2)
39
-
40
- if params_count > 2
41
- raise SeregaError, "Option :unless_value value should have up to 2 parameters (value, context)"
42
- end
37
+ signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: [:ctx])
38
+ raise SeregaError, signature_error unless valid_signature?(signature)
43
39
  end
44
40
 
45
41
  def must_be_callable
46
42
  "Invalid attribute option :unless_value. It must be a Symbol, a Proc or respond to :call"
47
43
  end
44
+
45
+ def valid_signature?(signature)
46
+ case signature
47
+ when "0" # no parameters
48
+ true
49
+ when "1" # (value)
50
+ true
51
+ when "2" # (value, context)
52
+ true
53
+ when "1_ctx" # (value, :ctx)
54
+ true
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ def signature_error
61
+ <<~ERROR.strip
62
+ Invalid attribute option :unless_value parameters, valid parameters signatures:
63
+ - () # no parameters
64
+ - (value) # one positional parameter
65
+ - (value, context) # two positional parameters
66
+ - (value, :ctx) # one positional parameter and :ctx keyword
67
+ ERROR
68
+ end
48
69
  end
49
70
  end
50
71
  end