serega 0.16.0 → 0.17.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -10
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +3 -3
  5. data/lib/serega/attribute_normalizer.rb +21 -2
  6. data/lib/serega/plugins/batch/lib/batch_config.rb +11 -8
  7. data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +26 -11
  8. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb +5 -35
  9. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +5 -35
  10. data/lib/serega/plugins/formatters/formatters.rb +88 -14
  11. data/lib/serega/plugins/if/if.rb +43 -19
  12. data/lib/serega/plugins/if/validations/check_opt_if.rb +4 -36
  13. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +7 -39
  14. data/lib/serega/plugins/if/validations/check_opt_unless.rb +4 -45
  15. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +7 -39
  16. data/lib/serega/plugins/metadata/meta_attribute.rb +21 -5
  17. data/lib/serega/plugins/metadata/metadata.rb +16 -7
  18. data/lib/serega/plugins/metadata/validations/check_block.rb +10 -10
  19. data/lib/serega/plugins/metadata/validations/check_opt_const.rb +38 -0
  20. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +61 -0
  21. data/lib/serega/plugins/metadata/validations/check_opts.rb +24 -10
  22. data/lib/serega/utils/params_count.rb +50 -0
  23. data/lib/serega/utils/to_hash.rb +1 -1
  24. data/lib/serega/validations/attribute/check_block.rb +4 -5
  25. data/lib/serega/validations/attribute/check_opt_value.rb +16 -12
  26. data/lib/serega/validations/check_attribute_params.rb +1 -0
  27. data/lib/serega/validations/utils/check_extra_keyword_arg.rb +33 -0
  28. data/lib/serega.rb +2 -0
  29. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b291b12115b8ca239642972b103f896f13f02874acdc5663a8f404e02673ca8d
4
- data.tar.gz: 18bd88ca4bd77ca147161189efe57b2c544da6cc1b460fc7c694855d4b22edc9
3
+ metadata.gz: 47eacc613b0a58af4dc552e09e0f55ed731f7e2a852e5c668794e7eb5128be69
4
+ data.tar.gz: bb9fec8432d12108e374bf15da74a230a4efda994835e9480e795b7222bb9ced
5
5
  SHA512:
6
- metadata.gz: d5c5114ed1c1b8b5a0714a340aca263983f5f8cdb24d15c8138124d64520ebafe03f6c64a7985f131ee399fab287ee578889b1affa1e2aa6f4ddacfbb9c8517e
7
- data.tar.gz: 4d8191c7081b4650e3328141ec3cc2dc9128a7c4da3a38107e5f2c107d1d70446b67e718a3ffa2ff76e700f80dbdf975da9512d089c86ec552c3dc8a390345d1
6
+ metadata.gz: d7536453cd03fd6e0f65faf22f85f5a12de61ef2bda50d674cd5b31ab1fba815c4b94d5118d82902f5a434d06112cfdc16757eefd334ba217946f9e920442e05
7
+ data.tar.gz: 9bcd5c46c1ffa32f95bc02e821e94acd46408e4e65a0861e4738454e24a57767105a5d3a9af48d52c4662a1030819963f11ee8c317c84ed179eb44b282a02c73
data/README.md CHANGED
@@ -73,7 +73,8 @@ class UserSerializer < Serega
73
73
  # Block is used to define attribute value
74
74
  attribute(:first_name) { |user| user.profile&.first_name }
75
75
 
76
- # Option :value can be used with callable object to define attribute value
76
+ # Option :value can be used with proc or callable object to define attribute value
77
+ attribute :first_name, value: UserProfile.new # must have #call method
77
78
  attribute :first_name, value: proc { |user| user.profile&.first_name }
78
79
 
79
80
  # Option :delegate can be used to define attribute value.
@@ -555,7 +556,7 @@ attribute :name, batch: { loader: :name_loader, key: :id, default: nil }
555
556
  `:batch` option must be a hash with this keys:
556
557
 
557
558
  - `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for
558
- batch of keys. Receives 3 parameters: keys, context, plan_point.
559
+ batch of keys. Receives 3 parameters: keys, context, plan.
559
560
  - `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
560
561
  Key is optional if plugin was defined with `default_key` option.
561
562
  - `default` (optional) - Default value for attribute.
@@ -715,17 +716,24 @@ Adds ability to describe metadata and adds it to serialized response
715
716
 
716
717
  Added class-level method `:meta_attribute`, to define metadata, it accepts:
717
718
 
718
- - *path [Array of Symbols] - nested hash keys.
719
- - **options [Hash] - defaults are `hide_nil: false, hide_empty: false`
720
- - &block [Proc] - describes value for current meta attribute
719
+ - `*path` [Array of Symbols] - nested hash keys.
720
+ - `**options` [Hash]
721
+
722
+ - `:const` - describes metadata value (if it is constant)
723
+ - `:value` - describes metadata value as any `#callable` instance
724
+ - `:hide_nil` - does not show metadata key if value is nil, `false` by default
725
+ - `:hide_empty`, does not show metadata key if value is nil or empty,
726
+ `false` by default
727
+
728
+ - `&block` [Proc] - describes value for current meta attribute
721
729
 
722
730
  ```ruby
723
731
  class AppSerializer < Serega
724
732
  plugin :root
725
733
  plugin :metadata
726
734
 
727
- meta_attribute(:version) { '1.2.3' }
728
- meta_attribute(:ab_tests, :names) { %i[foo bar] }
735
+ meta_attribute(:version, const: '1.2.3')
736
+ meta_attribute(:ab_tests, :names, value: ABTests.new.method(:names))
729
737
  meta_attribute(:meta, :paging, hide_nil: true) do |records, ctx|
730
738
  next unless records.respond_to?(:total_count)
731
739
 
@@ -738,7 +746,7 @@ class AppSerializer < Serega
738
746
  end
739
747
 
740
748
  AppSerializer.to_h(nil)
741
- # => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=>[:foo, :bar]}}
749
+ # => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=> ... }}
742
750
  ```
743
751
 
744
752
  ### Plugin :context_metadata
@@ -773,15 +781,18 @@ Allows to define `formatters` and apply them on attribute values.
773
781
 
774
782
  Config option `config.formatters.add` can be used to add formatters.
775
783
 
776
- Attribute option `:format` now can be used with name of formatter or with
784
+ Attribute option `:format` can be used with name of formatter or with
777
785
  callable instance.
778
786
 
787
+ Formatters can accept up to 2 parameters (formatted object, context)
788
+
779
789
  ```ruby
780
790
  class AppSerializer < Serega
781
791
  plugin :formatters, formatters: {
782
792
  iso8601: ->(value) { time.iso8601.round(6) },
783
793
  on_off: ->(value) { value ? 'ON' : 'OFF' },
784
- money: ->(value) { value.round(2) }
794
+ money: ->(value, ctx) { value / 10**ctx[:digits) }
795
+ date: DateTypeFormatter # callable
785
796
  }
786
797
  end
787
798
 
@@ -1029,6 +1040,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
1029
1040
  [batch]: #plugin-batch
1030
1041
  [camel_case]: #plugin-camel_case
1031
1042
  [context_metadata]: #plugin-context_metadata
1043
+ [depth_limit]: #plugin-depth_limit
1032
1044
  [formatters]: #plugin-formatters
1033
1045
  [metadata]: #plugin-metadata
1034
1046
  [preloads]: #plugin-preloads
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.16.0
1
+ 0.17.0
@@ -61,10 +61,10 @@ class Serega
61
61
  # Shows specified serializer class
62
62
  # @return [Serega, nil] Attribute serializer if exists
63
63
  def serializer
64
- ser = @serializer
65
- return ser if (ser.is_a?(Class) && (ser < Serega)) || !ser
64
+ serializer = @serializer
65
+ return serializer if (serializer.is_a?(Class) && (serializer < Serega)) || !serializer
66
66
 
67
- @serializer = ser.is_a?(String) ? Object.const_get(ser, false) : ser.call
67
+ @serializer = serializer.is_a?(String) ? Object.const_get(serializer, false) : serializer.call
68
68
  end
69
69
 
70
70
  #
@@ -97,8 +97,8 @@ class Serega
97
97
  # - plugin :formatters (wraps resulted block in formatter block and formats :const values)
98
98
  #
99
99
  def prepare_value_block
100
- init_block ||
101
- init_opts[:value] ||
100
+ prepare_init_block ||
101
+ prepare_value_option_block ||
102
102
  prepare_const_block ||
103
103
  prepare_delegate_block ||
104
104
  prepare_keyword_block
@@ -139,6 +139,25 @@ class Serega
139
139
  end
140
140
  end
141
141
 
142
+ def prepare_init_block
143
+ prepare_callable_proc(init_block)
144
+ end
145
+
146
+ def prepare_value_option_block
147
+ prepare_callable_proc(init_opts[:value])
148
+ end
149
+
150
+ def prepare_callable_proc(callable)
151
+ return unless callable
152
+
153
+ params_count = SeregaUtils::ParamsCount.call(callable, max_count: 2)
154
+ case params_count
155
+ when 0 then proc { |obj, _ctx| callable.call }
156
+ when 1 then proc { |obj, _ctx| callable.call(obj) }
157
+ else callable
158
+ end
159
+ end
160
+
142
161
  def prepare_delegate_block
143
162
  delegate = init_opts[:delegate]
144
163
  return unless delegate
@@ -22,17 +22,20 @@ class Serega
22
22
  #
23
23
  # @return [void]
24
24
  #
25
- def define(loader_name, &block)
26
- unless block
27
- raise SeregaError, "Block must be given to #define method"
25
+ def define(loader_name, callable = nil, &block)
26
+ if (!callable && !block) || (callable && block)
27
+ raise SeregaError, "Batch loader can be specified with one of arguments - callable value or &block"
28
28
  end
29
29
 
30
- params = block.parameters
31
- if params.count > 3 || !params.all? { |param| (param[0] == :req) || (param[0] == :opt) }
32
- raise SeregaError, "Block can have maximum 3 regular parameters"
30
+ callable ||= block
31
+ SeregaValidations::Utils::CheckExtraKeywordArg.call(callable, "batch loader `#{loader_name}`")
32
+ params_count = SeregaUtils::ParamsCount.call(callable, max_count: 3)
33
+
34
+ if params_count > 3
35
+ raise SeregaError, "Batch loader can have maximum 3 parameters (keys, context, plan)"
33
36
  end
34
37
 
35
- loaders[loader_name] = block
38
+ loaders[loader_name] = callable
36
39
  end
37
40
 
38
41
  # Shows defined loaders
@@ -48,7 +51,7 @@ class Serega
48
51
  #
49
52
  # @return [Proc] batch loader block
50
53
  def fetch_loader(loader_name)
51
- loaders[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch.define(:#{loader_name}) { |keys, ctx, points| ... }")
54
+ loaders[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch.define(:#{loader_name}) { |keys| ... }")
52
55
  end
53
56
 
54
57
  # Shows option to auto hide attributes with :batch specified
@@ -42,22 +42,37 @@ class Serega
42
42
  batch = init_opts[:batch]
43
43
  return unless batch
44
44
 
45
- # take loader
46
- loader = batch[:loader]
45
+ loader = prepare_batch_loader(batch[:loader])
47
46
 
48
- # take key
49
47
  key = batch[:key] || self.class.serializer_class.config.batch.default_key
50
- proc_key =
51
- if key.is_a?(Symbol)
52
- proc { |object| object.public_send(key) }
53
- else
54
- key
55
- end
48
+ key = prepare_batch_key(key)
56
49
 
57
- # take default value
58
50
  default = batch.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
59
51
 
60
- {loader: loader, key: proc_key, default: default}
52
+ {loader: loader, key: key, default: default}
53
+ end
54
+
55
+ def prepare_batch_key(key)
56
+ return proc { |object| object.public_send(key) } if key.is_a?(Symbol)
57
+
58
+ params_count = SeregaUtils::ParamsCount.call(key, max_count: 2)
59
+ case params_count
60
+ when 0 then proc { key.call }
61
+ when 1 then proc { |object| key.call(object) }
62
+ else key
63
+ end
64
+ end
65
+
66
+ def prepare_batch_loader(loader)
67
+ return loader if loader.is_a?(Symbol)
68
+
69
+ params_count = SeregaUtils::ParamsCount.call(loader, max_count: 3)
70
+ case params_count
71
+ when 0 then proc { loader.call }
72
+ when 1 then proc { |object| loader.call(object) }
73
+ when 2 then proc { |object, context| loader.call(object, context) }
74
+ else loader
75
+ end
61
76
  end
62
77
  end
63
78
  end
@@ -22,45 +22,15 @@ class Serega
22
22
 
23
23
  raise SeregaError, must_be_callable unless key.respond_to?(:call)
24
24
 
25
- if key.is_a?(Proc)
26
- check_block(key)
27
- else
28
- check_callable(key)
29
- end
25
+ SeregaValidations::Utils::CheckExtraKeywordArg.call(key, "batch option :key")
26
+ params_count = SeregaUtils::ParamsCount.call(key, max_count: 2)
27
+ raise SeregaError, params_count_error if params_count > 2
30
28
  end
31
29
 
32
30
  private
33
31
 
34
- def check_block(block)
35
- return if valid_parameters?(block, accepted_count: 0..2)
36
-
37
- raise SeregaError, block_parameters_error
38
- end
39
-
40
- def check_callable(callable)
41
- return if valid_parameters?(callable.method(:call), accepted_count: 2..2)
42
-
43
- raise SeregaError, callable_parameters_error
44
- end
45
-
46
- def valid_parameters?(data, accepted_count:)
47
- params = data.parameters
48
- accepted_count.include?(params.count) && valid_parameters_types?(params)
49
- end
50
-
51
- def valid_parameters_types?(params)
52
- params.all? do |param|
53
- type = param[0]
54
- (type == :req) || (type == :opt)
55
- end
56
- end
57
-
58
- def block_parameters_error
59
- "Invalid :batch option :key. When it is a Proc it can have maximum two regular parameters (object, context)"
60
- end
61
-
62
- def callable_parameters_error
63
- "Invalid :batch option :key. When it is a callable object it must have two regular parameters (object, context)"
32
+ def params_count_error
33
+ "Invalid :batch option :key. It can accept maximum 2 parameters (object, context)"
64
34
  end
65
35
 
66
36
  def must_be_callable
@@ -22,45 +22,15 @@ class Serega
22
22
 
23
23
  raise SeregaError, must_be_callable unless loader.respond_to?(:call)
24
24
 
25
- if loader.is_a?(Proc)
26
- check_block(loader)
27
- else
28
- check_callable(loader)
29
- end
25
+ SeregaValidations::Utils::CheckExtraKeywordArg.call(loader, ":batch option :loader")
26
+ params_count = SeregaUtils::ParamsCount.call(loader, max_count: 3)
27
+ raise SeregaError, params_count_error if params_count > 3
30
28
  end
31
29
 
32
30
  private
33
31
 
34
- def check_block(block)
35
- return if valid_parameters?(block, accepted_count: 0..3)
36
-
37
- raise SeregaError, block_parameters_error
38
- end
39
-
40
- def check_callable(callable)
41
- return if valid_parameters?(callable.method(:call), accepted_count: 3..3)
42
-
43
- raise SeregaError, callable_parameters_error
44
- end
45
-
46
- def valid_parameters?(data, accepted_count:)
47
- params = data.parameters
48
- accepted_count.include?(params.count) && valid_parameters_types?(params)
49
- end
50
-
51
- def valid_parameters_types?(params)
52
- params.all? do |param|
53
- type = param[0]
54
- (type == :req) || (type == :opt)
55
- end
56
- end
57
-
58
- def block_parameters_error
59
- "Invalid :batch option :loader. When it is a Proc it can have maximum three regular parameters (keys, context, point)"
60
- end
61
-
62
- def callable_parameters_error
63
- "Invalid :batch option :loader. When it is a callable object it must have three regular parameters (keys, context, point)"
32
+ def params_count_error
33
+ "Invalid :batch option :loader. It can accept maximum 3 parameters (keys, context, plan)"
64
34
  end
65
35
 
66
36
  def must_be_callable
@@ -11,12 +11,15 @@ class Serega
11
11
  #
12
12
  # Attribute option `:format` now can be used with name of formatter or with callable instance.
13
13
  #
14
+ # Formatters can accept up to 2 parameters (formatted object, context)
15
+ #
14
16
  # @example
15
17
  # class AppSerializer < Serega
16
18
  # plugin :formatters, formatters: {
17
19
  # iso8601: ->(value) { time.iso8601.round(6) },
18
20
  # on_off: ->(value) { value ? 'ON' : 'OFF' },
19
21
  # money: ->(value) { value.round(2) }
22
+ # date: DateTypeFormatter # callable
20
23
  # }
21
24
  # end
22
25
  #
@@ -35,6 +38,7 @@ class Serega
35
38
  # attribute :updated_at, format: :iso8601
36
39
  #
37
40
  # # Using `callable` formatter
41
+ # attribute :score_percent, format: PercentFormmatter # callable class
38
42
  # attribute :score_percent, format: proc { |percent| "#{percent.round(2)}%" }
39
43
  # end
40
44
  #
@@ -68,6 +72,7 @@ class Serega
68
72
  def self.load_plugin(serializer_class, **_opts)
69
73
  serializer_class::SeregaConfig.include(ConfigInstanceMethods)
70
74
  serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
75
+ serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
71
76
  end
72
77
 
73
78
  #
@@ -107,6 +112,7 @@ class Serega
107
112
  # @return [void]
108
113
  def add(formatters)
109
114
  formatters.each_pair do |key, value|
115
+ CheckFormatter.call(key, value)
110
116
  opts[key] = value
111
117
  end
112
118
  end
@@ -124,6 +130,21 @@ class Serega
124
130
  end
125
131
  end
126
132
 
133
+ #
134
+ # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
135
+ #
136
+ # @see Serega::SeregaValidations::CheckAttributeParams
137
+ #
138
+ module CheckAttributeParamsInstanceMethods
139
+ private
140
+
141
+ def check_opts
142
+ super
143
+
144
+ CheckOptFormat.call(opts, self.class.serializer_class)
145
+ end
146
+ end
147
+
127
148
  #
128
149
  # Attribute class additional/patched instance methods
129
150
  #
@@ -143,16 +164,10 @@ class Serega
143
164
  def prepare_value_block
144
165
  return super unless formatter
145
166
 
146
- if init_opts.key?(:const)
147
- # Format const value in advance
148
- const_value = formatter.call(init_opts[:const])
149
- proc { const_value }
150
- else
151
- # Wrap original block into formatter block
152
- proc do |object, context|
153
- value = super.call(object, context)
154
- formatter.call(value)
155
- end
167
+ # Wrap original block into formatter block
168
+ proc do |object, context|
169
+ value = super.call(object, context)
170
+ formatter.call(value, context)
156
171
  end
157
172
  end
158
173
 
@@ -160,10 +175,69 @@ class Serega
160
175
  formatter = init_opts[:format]
161
176
  return unless formatter
162
177
 
163
- if formatter.is_a?(Symbol)
164
- self.class.serializer_class.config.formatters.opts.fetch(formatter)
165
- else
166
- formatter # already callable
178
+ formatter = self.class.serializer_class.config.formatters.opts.fetch(formatter) if formatter.is_a?(Symbol)
179
+ prepare_callable_proc(formatter)
180
+ end
181
+ end
182
+
183
+ #
184
+ # Validator for attribute :format option
185
+ #
186
+ class CheckOptFormat
187
+ class << self
188
+ #
189
+ # Checks attribute :format option must be registered or valid callable with maximum 2 args
190
+ #
191
+ # @param opts [value] Attribute options
192
+ #
193
+ # @raise [SeregaError] Attribute validation error
194
+ #
195
+ # @return [void]
196
+ #
197
+ def call(opts, serializer_class)
198
+ return unless opts.key?(:format)
199
+
200
+ formatter = opts[:format]
201
+
202
+ if formatter.is_a?(Symbol)
203
+ check_formatter_defined(serializer_class, formatter)
204
+ else
205
+ CheckFormatter.call(:format, formatter)
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ def check_formatter_defined(serializer_class, formatter)
212
+ return if serializer_class.config.formatters.opts.key?(formatter)
213
+
214
+ raise Serega::SeregaError, "Formatter `#{formatter.inspect}` was not defined"
215
+ end
216
+ end
217
+ end
218
+
219
+ #
220
+ # Validator for formatters defined as config options or directly as attribute :format option
221
+ #
222
+ class CheckFormatter
223
+ class << self
224
+ #
225
+ # Check formatter type and parameters
226
+ #
227
+ # @param formatter_name [Symbol] Name of formatter
228
+ # @param formatter [#call] Formatter callable object
229
+ #
230
+ # @return [void]
231
+ #
232
+ def call(formatter_name, formatter)
233
+ raise Serega::SeregaError, "Option #{formatter_name.inspect} must have callable value" unless formatter.respond_to?(:call)
234
+
235
+ SeregaValidations::Utils::CheckExtraKeywordArg.call(formatter, "#{formatter_name.inspect} value")
236
+ params_count = SeregaUtils::ParamsCount.call(formatter, max_count: 2)
237
+
238
+ if params_count > 2
239
+ raise SeregaError, "Formatter can have maximum 2 parameters (value to format, context)"
240
+ end
167
241
  end
168
242
  end
169
243
  end
@@ -61,10 +61,11 @@ class Serega
61
61
  require_relative "validations/check_opt_unless"
62
62
  require_relative "validations/check_opt_unless_value"
63
63
 
64
- serializer_class::SeregaAttribute.include(SeregaAttributeInstanceMethods)
64
+ serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
65
+ serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
65
66
  serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
66
67
  serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
67
- serializer_class::SeregaObjectSerializer.include(SeregaObjectSerializerInstanceMethods)
68
+ serializer_class::SeregaObjectSerializer.include(ObjectSerializerInstanceMethods)
68
69
  end
69
70
 
70
71
  #
@@ -79,12 +80,47 @@ class Serega
79
80
  serializer_class.config.attribute_keys << :if << :if_value << :unless << :unless_value
80
81
  end
81
82
 
83
+ #
84
+ # SeregaAttributeNormalizer additional/patched instance methods
85
+ #
86
+ # @see SeregaAttributeNormalizer::AttributeInstanceMethods
87
+ #
88
+ module AttributeNormalizerInstanceMethods
89
+ #
90
+ # Returns prepared attribute :if_options.
91
+ #
92
+ # @return [Hash] prepared options for :if plugin
93
+ #
94
+ def if_options
95
+ @if_options ||= {
96
+ if: prepare_if_option(init_opts[:if]),
97
+ unless: prepare_if_option(init_opts[:unless]),
98
+ if_value: prepare_if_option(init_opts[:if_value]),
99
+ unless_value: prepare_if_option(init_opts[:unless_value])
100
+ }.freeze
101
+ end
102
+
103
+ private
104
+
105
+ def prepare_if_option(if_option)
106
+ return unless if_option
107
+ return proc { |val| val.public_send(if_option) } if if_option.is_a?(Symbol)
108
+
109
+ params_count = SeregaUtils::ParamsCount.call(if_option, max_count: 2)
110
+ case params_count
111
+ when 0 then proc { if_option.call }
112
+ when 1 then proc { |obj| if_option.call(obj) }
113
+ else if_option
114
+ end
115
+ end
116
+ end
117
+
82
118
  #
83
119
  # SeregaAttribute additional/patched instance methods
84
120
  #
85
121
  # @see Serega::SeregaAttribute
86
122
  #
87
- module SeregaAttributeInstanceMethods
123
+ module AttributeInstanceMethods
88
124
  # @return provided :if options
89
125
  attr_reader :opt_if
90
126
 
@@ -92,7 +128,7 @@ class Serega
92
128
 
93
129
  def set_normalized_vars(normalizer)
94
130
  super
95
- @opt_if = initials[:opts].slice(:if, :if_value, :unless, :unless_value).freeze
131
+ @opt_if = normalizer.if_options
96
132
  end
97
133
  end
98
134
 
@@ -125,20 +161,8 @@ class Serega
125
161
  opt_unless = attribute.opt_if[opt_unless_name]
126
162
  return true if opt_if.nil? && opt_unless.nil?
127
163
 
128
- res_if =
129
- case opt_if
130
- when NilClass then true
131
- when Symbol then obj.public_send(opt_if)
132
- else opt_if.call(obj, ctx)
133
- end
134
-
135
- res_unless =
136
- case opt_unless
137
- when NilClass then true
138
- when Symbol then !obj.public_send(opt_unless)
139
- else !opt_unless.call(obj, ctx)
140
- end
141
-
164
+ res_if = opt_if ? opt_if.call(obj, ctx) : true
165
+ res_unless = opt_unless ? !opt_unless.call(obj, ctx) : true
142
166
  res_if && res_unless
143
167
  end
144
168
  end
@@ -166,7 +190,7 @@ class Serega
166
190
  #
167
191
  # @see Serega::SeregaObjectSerializer
168
192
  #
169
- module SeregaObjectSerializerInstanceMethods
193
+ module ObjectSerializerInstanceMethods
170
194
  private
171
195
 
172
196
  def serialize_point(object, point, _container)
@@ -27,48 +27,16 @@ class Serega
27
27
 
28
28
  def check_type(value)
29
29
  return if value.is_a?(Symbol)
30
-
31
30
  raise SeregaError, must_be_callable unless value.respond_to?(:call)
32
31
 
33
- if value.is_a?(Proc)
34
- check_block(value)
35
- else
36
- check_callable(value)
37
- end
38
- end
39
-
40
- def check_block(block)
41
- return if valid_parameters?(block, accepted_count: 0..2)
32
+ SeregaValidations::Utils::CheckExtraKeywordArg.call(value, ":if option")
33
+ params_count = SeregaUtils::ParamsCount.call(value, max_count: 2)
42
34
 
43
- raise SeregaError, block_parameters_error
44
- end
45
-
46
- def check_callable(callable)
47
- return if valid_parameters?(callable.method(:call), accepted_count: 2..2)
48
-
49
- raise SeregaError, callable_parameters_error
50
- end
51
-
52
- def valid_parameters?(data, accepted_count:)
53
- params = data.parameters
54
- accepted_count.include?(params.count) && valid_parameters_types?(params)
55
- end
56
-
57
- def valid_parameters_types?(params)
58
- params.all? do |param|
59
- type = param[0]
60
- (type == :req) || (type == :opt)
35
+ if params_count > 2
36
+ raise SeregaError, "Option :if value should have up to 2 parameters (object, context)"
61
37
  end
62
38
  end
63
39
 
64
- def block_parameters_error
65
- "Invalid attribute option :if. When it is a Proc it can have maximum two regular parameters (object, context)"
66
- end
67
-
68
- def callable_parameters_error
69
- "Invalid attribute option :if. When it is a callable object it must have two regular parameters (object, context)"
70
- end
71
-
72
40
  def must_be_callable
73
41
  "Invalid attribute option :if. It must be a Symbol, a Proc or respond to :call"
74
42
  end