active_interaction 4.0.5 → 5.0.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -6
  3. data/README.md +67 -32
  4. data/lib/active_interaction/array_input.rb +77 -0
  5. data/lib/active_interaction/base.rb +14 -98
  6. data/lib/active_interaction/concerns/active_recordable.rb +3 -3
  7. data/lib/active_interaction/concerns/missable.rb +2 -2
  8. data/lib/active_interaction/errors.rb +6 -88
  9. data/lib/active_interaction/exceptions.rb +47 -0
  10. data/lib/active_interaction/filter/column.rb +59 -0
  11. data/lib/active_interaction/filter/error.rb +40 -0
  12. data/lib/active_interaction/filter.rb +44 -53
  13. data/lib/active_interaction/filters/abstract_date_time_filter.rb +9 -6
  14. data/lib/active_interaction/filters/abstract_numeric_filter.rb +7 -3
  15. data/lib/active_interaction/filters/array_filter.rb +36 -10
  16. data/lib/active_interaction/filters/boolean_filter.rb +4 -3
  17. data/lib/active_interaction/filters/date_filter.rb +1 -1
  18. data/lib/active_interaction/filters/date_time_filter.rb +1 -1
  19. data/lib/active_interaction/filters/decimal_filter.rb +1 -1
  20. data/lib/active_interaction/filters/float_filter.rb +1 -1
  21. data/lib/active_interaction/filters/hash_filter.rb +23 -15
  22. data/lib/active_interaction/filters/integer_filter.rb +1 -1
  23. data/lib/active_interaction/filters/interface_filter.rb +12 -12
  24. data/lib/active_interaction/filters/object_filter.rb +9 -3
  25. data/lib/active_interaction/filters/record_filter.rb +21 -11
  26. data/lib/active_interaction/filters/string_filter.rb +1 -1
  27. data/lib/active_interaction/filters/symbol_filter.rb +1 -1
  28. data/lib/active_interaction/filters/time_filter.rb +4 -4
  29. data/lib/active_interaction/hash_input.rb +43 -0
  30. data/lib/active_interaction/input.rb +23 -0
  31. data/lib/active_interaction/inputs.rb +157 -46
  32. data/lib/active_interaction/locale/en.yml +0 -1
  33. data/lib/active_interaction/locale/fr.yml +0 -1
  34. data/lib/active_interaction/locale/it.yml +0 -1
  35. data/lib/active_interaction/locale/ja.yml +0 -1
  36. data/lib/active_interaction/locale/pt-BR.yml +0 -1
  37. data/lib/active_interaction/modules/validation.rb +6 -17
  38. data/lib/active_interaction/version.rb +1 -1
  39. data/lib/active_interaction.rb +43 -36
  40. data/spec/active_interaction/array_input_spec.rb +166 -0
  41. data/spec/active_interaction/base_spec.rb +15 -240
  42. data/spec/active_interaction/concerns/active_modelable_spec.rb +3 -3
  43. data/spec/active_interaction/concerns/active_recordable_spec.rb +7 -7
  44. data/spec/active_interaction/concerns/hashable_spec.rb +8 -8
  45. data/spec/active_interaction/concerns/missable_spec.rb +9 -9
  46. data/spec/active_interaction/concerns/runnable_spec.rb +34 -32
  47. data/spec/active_interaction/errors_spec.rb +60 -43
  48. data/spec/active_interaction/{filter_column_spec.rb → filter/column_spec.rb} +3 -10
  49. data/spec/active_interaction/filter_spec.rb +6 -6
  50. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +2 -2
  51. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +2 -2
  52. data/spec/active_interaction/filters/array_filter_spec.rb +99 -24
  53. data/spec/active_interaction/filters/boolean_filter_spec.rb +12 -11
  54. data/spec/active_interaction/filters/date_filter_spec.rb +32 -27
  55. data/spec/active_interaction/filters/date_time_filter_spec.rb +34 -29
  56. data/spec/active_interaction/filters/decimal_filter_spec.rb +20 -18
  57. data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
  58. data/spec/active_interaction/filters/float_filter_spec.rb +19 -17
  59. data/spec/active_interaction/filters/hash_filter_spec.rb +16 -18
  60. data/spec/active_interaction/filters/integer_filter_spec.rb +24 -22
  61. data/spec/active_interaction/filters/interface_filter_spec.rb +105 -82
  62. data/spec/active_interaction/filters/object_filter_spec.rb +52 -36
  63. data/spec/active_interaction/filters/record_filter_spec.rb +61 -39
  64. data/spec/active_interaction/filters/string_filter_spec.rb +7 -7
  65. data/spec/active_interaction/filters/symbol_filter_spec.rb +6 -6
  66. data/spec/active_interaction/filters/time_filter_spec.rb +57 -34
  67. data/spec/active_interaction/hash_input_spec.rb +58 -0
  68. data/spec/active_interaction/i18n_spec.rb +22 -17
  69. data/spec/active_interaction/inputs_spec.rb +167 -23
  70. data/spec/active_interaction/integration/array_interaction_spec.rb +3 -7
  71. data/spec/active_interaction/modules/validation_spec.rb +8 -31
  72. data/spec/spec_helper.rb +8 -0
  73. data/spec/support/concerns.rb +2 -2
  74. data/spec/support/filters.rb +27 -51
  75. data/spec/support/interactions.rb +4 -4
  76. metadata +45 -95
  77. data/lib/active_interaction/filter_column.rb +0 -57
@@ -30,7 +30,7 @@ module ActiveInteraction
30
30
  include ActiveRecordable
31
31
  include Runnable
32
32
 
33
- define_callbacks :type_check
33
+ define_callbacks :filter
34
34
 
35
35
  class << self
36
36
  include Hashable
@@ -43,7 +43,7 @@ module ActiveInteraction
43
43
  #
44
44
  # Runs validations and if there are no errors it will call {#execute}.
45
45
  #
46
- # @param (see ActiveInteraction::Inputs.process)
46
+ # @param input [Hash, ActionController::Parameters]
47
47
  #
48
48
  # @return [Base]
49
49
 
@@ -88,7 +88,8 @@ module ActiveInteraction
88
88
  # rubocop:enable Naming/MemoizedInstanceVariableName
89
89
  end
90
90
 
91
- # @private
91
+ private
92
+
92
93
  # rubocop:disable Style/MissingRespondToMissing
93
94
  def method_missing(*args, &block)
94
95
  super do |klass, names, options|
@@ -99,8 +100,6 @@ module ActiveInteraction
99
100
  end
100
101
  # rubocop:enable Style/MissingRespondToMissing
101
102
 
102
- private
103
-
104
103
  # @param klass [Class]
105
104
  # @param name [Symbol]
106
105
  # @param options [Hash]
@@ -162,7 +161,9 @@ module ActiveInteraction
162
161
  def initialize(inputs = {})
163
162
  @_interaction_raw_inputs = inputs
164
163
 
165
- populate_filters_and_inputs(Inputs.process(inputs))
164
+ @_interaction_inputs = Inputs.new(inputs, self) do |name, input|
165
+ public_send("#{name}=", input.value)
166
+ end
166
167
  end
167
168
 
168
169
  # @!method compose(other, inputs = {})
@@ -186,113 +187,28 @@ module ActiveInteraction
186
187
  # Returns the inputs provided to {.run} or {.run!} after being cast based
187
188
  # on the filters in the class.
188
189
  #
189
- # @return [Hash{Symbol => Object}] All inputs passed to {.run} or {.run!}.
190
+ # @return [Inputs] All expected inputs passed to {.run} or {.run!}.
190
191
  def inputs
191
192
  @_interaction_inputs
192
193
  end
193
194
 
194
- # Returns `true` if the given key was in the hash passed to {.run}.
195
- # Otherwise returns `false`. Use this to figure out if an input was given,
196
- # even if it was `nil`. Keys within nested hash filter can also be checked
197
- # by passing them in series. Arrays can be checked in the same manor as
198
- # hashes by passing an index.
199
- #
200
- # @example
201
- # class Example < ActiveInteraction::Base
202
- # integer :x, default: nil
203
- # def execute; given?(:x) end
204
- # end
205
- # Example.run!() # => false
206
- # Example.run!(x: nil) # => true
207
- # Example.run!(x: rand) # => true
208
- #
209
- # @example Nested checks
210
- # class Example < ActiveInteraction::Base
211
- # hash :x, default: {} do
212
- # integer :y, default: nil
213
- # end
214
- # array :a, default: [] do
215
- # integer
216
- # end
217
- # def execute; given?(:x, :y) || given?(:a, 2) end
218
- # end
219
- # Example.run!() # => false
220
- # Example.run!(x: nil) # => false
221
- # Example.run!(x: {}) # => false
222
- # Example.run!(x: { y: nil }) # => true
223
- # Example.run!(x: { y: rand }) # => true
224
- # Example.run!(a: [1, 2]) # => false
225
- # Example.run!(a: [1, 2, 3]) # => true
226
- #
227
- # @param input [#to_sym]
228
- #
229
- # @return [Boolean]
230
- #
231
- # @since 2.1.0
232
- # rubocop:disable all
233
- def given?(input, *rest)
234
- filter_level = self.class
235
- input_level = @_interaction_raw_inputs
236
-
237
- [input, *rest].each do |key_or_index|
238
- if key_or_index.is_a?(Symbol) || key_or_index.is_a?(String)
239
- key = key_or_index.to_sym
240
- key_to_s = key_or_index.to_s
241
- filter_level = filter_level.filters[key]
242
-
243
- break false if filter_level.nil? || input_level.nil?
244
- if filter_level.accepts_grouped_inputs?
245
- break false unless input_level.key?(key) || input_level.key?(key_to_s) || Inputs.keys_for_group?(input_level.keys, key)
246
- else
247
- break false unless input_level.key?(key) || input_level.key?(key_to_s)
248
- end
249
-
250
- input_level = input_level[key] || input_level[key_to_s]
251
- else
252
- index = key_or_index
253
- filter_level = filter_level.filters.first.last
254
-
255
- break false if filter_level.nil? || input_level.nil?
256
- break false unless index.between?(-input_level.size, input_level.size - 1)
257
-
258
- input_level = input_level[index]
259
- end
260
- end && true
195
+ # @private
196
+ def read_attribute_for_validation(attribute)
197
+ super(errors.local_attribute(attribute))
261
198
  end
262
- # rubocop:enable all
263
199
 
264
200
  protected
265
201
 
266
202
  def run_validations!
267
- type_check
203
+ filter
268
204
 
269
205
  super if errors.empty?
270
206
  end
271
207
 
272
208
  private
273
209
 
274
- def populate_filters_and_inputs(inputs)
275
- @_interaction_inputs = Inputs.new
276
-
277
- self.class.filters.each do |name, filter|
278
- value =
279
- begin
280
- filter.clean(inputs[name], self)
281
- rescue InvalidValueError, MissingValueError, NoDefaultError
282
- # #type_check will add errors if appropriate.
283
- # We'll get the original value for the error.
284
- inputs[name]
285
- end
286
-
287
- @_interaction_inputs[name] = value
288
- public_send("#{name}=", value)
289
- end
290
-
291
- @_interaction_inputs.freeze
292
- end
293
-
294
- def type_check
295
- run_callbacks(:type_check) do
210
+ def filter
211
+ run_callbacks(:filter) do
296
212
  Validation.validate(self, self.class.filters, inputs).each do |attr, type, kwargs = {}|
297
213
  errors.add(attr, type, **kwargs)
298
214
  end
@@ -17,15 +17,15 @@ module ActiveInteraction
17
17
  # end
18
18
  #
19
19
  # Interaction.new.column_for_attribute(:email)
20
- # # => #<ActiveInteraction::FilterColumn:0x007faebeb2a6c8 @type=:string>
20
+ # # => #<ActiveInteraction::Filter::Column:0x007faebeb2a6c8 @type=:string>
21
21
  #
22
22
  # Interaction.new.column_for_attribute(:not_a_filter)
23
23
  # # => nil
24
24
  #
25
- # @return [FilterColumn, nil]
25
+ # @return [Filter::Column, nil]
26
26
  def column_for_attribute(name)
27
27
  filter = self.class.filters[name]
28
- FilterColumn.intern(filter.database_column_type) if filter
28
+ Filter::Column.intern(filter.database_column_type) if filter
29
29
  end
30
30
 
31
31
  # Returns true if a filter of that name exists.
@@ -7,6 +7,8 @@ module ActiveInteraction
7
7
  module Missable
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ private
11
+
10
12
  # @param slug [Symbol]
11
13
  #
12
14
  # @yield [klass, args, options]
@@ -26,8 +28,6 @@ module ActiveInteraction
26
28
  self
27
29
  end
28
30
 
29
- private
30
-
31
31
  # @param slug [Symbol]
32
32
  #
33
33
  # @return [Filter, nil]
@@ -1,93 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveInteraction
4
- # Top-level error class. All other errors subclass this.
5
- #
6
- # @return [Class]
7
- Error = Class.new(StandardError)
8
-
9
- # Raised if a constant name is invalid.
10
- #
11
- # @return [Class]
12
- InvalidNameError = Class.new(Error)
13
-
14
- # Raised if a converter is invalid.
15
- #
16
- # @return [Class]
17
- InvalidConverterError = Class.new(Error)
18
-
19
- # Raised if a default value is invalid.
20
- #
21
- # @return [Class]
22
- InvalidDefaultError = Class.new(Error)
23
-
24
- # Raised if a filter has an invalid definition.
25
- #
26
- # @return [Class]
27
- InvalidFilterError = Class.new(Error)
28
-
29
- # Raised if an interaction is invalid.
30
- #
31
- # @return [Class]
32
- class InvalidInteractionError < Error
33
- attr_accessor :interaction
34
- end
35
-
36
- # Raised if a user-supplied value is invalid.
37
- #
38
- # @return [Class]
39
- InvalidValueError = Class.new(Error)
40
-
41
- # Raised if a filter cannot be found.
42
- #
43
- # @return [Class]
44
- MissingFilterError = Class.new(Error)
45
-
46
- # Raised if no value is given.
47
- #
48
- # @return [Class]
49
- MissingValueError = Class.new(Error)
50
-
51
- # Raised if there is no default value.
52
- #
53
- # @return [Class]
54
- NoDefaultError = Class.new(Error)
55
-
56
- # Raised if a user-supplied value to a nested hash input is invalid.
57
- #
58
- # @return [Class]
59
- class InvalidNestedValueError < InvalidValueError
60
- # @return [Symbol]
61
- attr_reader :filter_name
62
-
63
- # @return [Object]
64
- attr_reader :input_value
65
-
66
- # @param filter_name [Symbol]
67
- # @param input_value [Object]
68
- def initialize(filter_name, input_value)
69
- super("#{filter_name}: #{input_value.inspect}")
70
-
71
- @filter_name = filter_name
72
- @input_value = input_value
73
- end
74
- end
75
-
76
- # Used by {Runnable} to signal a failure when composing.
77
- #
78
- # @private
79
- class Interrupt < Error
80
- attr_reader :errors
81
-
82
- # @param errors [Runnable]
83
- def initialize(errors)
84
- super()
85
-
86
- @errors = errors
87
- end
88
- end
89
- private_constant :Interrupt
90
-
91
4
  # An extension that provides the ability to merge other errors into itself.
92
5
  class Errors < ActiveModel::Errors
93
6
  attr_accessor :backtrace
@@ -103,10 +16,15 @@ module ActiveInteraction
103
16
  self
104
17
  end
105
18
 
19
+ # @private
20
+ def local_attribute(attribute)
21
+ attribute.to_s.sub(/\A([^.\[]*).*\z/, '\1').to_sym
22
+ end
23
+
106
24
  private
107
25
 
108
26
  def attribute?(attribute)
109
- @base.respond_to?(attribute)
27
+ @base.respond_to?(local_attribute(attribute))
110
28
  end
111
29
 
112
30
  def detailed_error?(detail)
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteraction
4
+ # Top-level error class. All other errors subclass this.
5
+ Error = Class.new(StandardError)
6
+
7
+ # Raised if a constant name is invalid.
8
+ InvalidNameError = Class.new(Error)
9
+
10
+ # Raised if a converter is invalid.
11
+ InvalidConverterError = Class.new(Error)
12
+
13
+ # Raised if a default value is invalid.
14
+ InvalidDefaultError = Class.new(Error)
15
+
16
+ # Raised if a filter has an invalid definition.
17
+ InvalidFilterError = Class.new(Error)
18
+
19
+ # Raised if an interaction is invalid.
20
+ class InvalidInteractionError < Error
21
+ # The interaction where the error occured.
22
+ #
23
+ # @return [ActiveInteraction::Base]
24
+ attr_accessor :interaction
25
+ end
26
+
27
+ # Raised if a filter cannot be found.
28
+ MissingFilterError = Class.new(Error)
29
+
30
+ # Raised if there is no default value.
31
+ NoDefaultError = Class.new(Error)
32
+
33
+ # Used by {Runnable} to signal a failure when composing.
34
+ #
35
+ # @private
36
+ class Interrupt < Error
37
+ attr_reader :errors
38
+
39
+ # @param errors [Runnable]
40
+ def initialize(errors)
41
+ super()
42
+
43
+ @errors = errors
44
+ end
45
+ end
46
+ private_constant :Interrupt
47
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteraction
4
+ class Filter
5
+ # A minimal implementation of an `ActiveRecord::ConnectionAdapters::Column`.
6
+ class Column
7
+ # @return [nil]
8
+ attr_reader :limit
9
+
10
+ # @return [Symbol]
11
+ attr_reader :type
12
+
13
+ class << self
14
+ # Find or create the `Filter::Column` for a specific type.
15
+ #
16
+ # @param type [Symbol] A database column type.
17
+ #
18
+ # @example
19
+ # Filter::Column.intern(:string)
20
+ # # => #<ActiveInteraction::Filter::Column:0x007feeaa649c @type=:string>
21
+ #
22
+ # Filter::Column.intern(:string)
23
+ # # => #<ActiveInteraction::Filter::Column:0x007feeaa649c @type=:string>
24
+ #
25
+ # Filter::Column.intern(:boolean)
26
+ # # => #<ActiveInteraction::Filter::Column:0x007feeab8a08 @type=:boolean>
27
+ #
28
+ # @return [Filter::Column]
29
+ def intern(type)
30
+ @columns ||= {}
31
+ @columns[type] ||= new(type)
32
+ end
33
+
34
+ private :new
35
+ end
36
+
37
+ # @param type [type] The database column type.
38
+ #
39
+ # @private
40
+ def initialize(type)
41
+ @type = type
42
+ end
43
+
44
+ # Returns `true` if the column is either of type :integer or :float.
45
+ #
46
+ # @return [Boolean]
47
+ def number?
48
+ %i[integer float].include?(type)
49
+ end
50
+
51
+ # Returns `true` if the column is of type :string.
52
+ #
53
+ # @return [Boolean]
54
+ def text?
55
+ type == :string
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module ActiveInteraction
6
+ class Filter
7
+ # A validation error that occurs while processing the filter.
8
+ class Error
9
+ # @private
10
+ def initialize(filter, type, name: nil)
11
+ @filter = filter
12
+ @name = name || filter.name
13
+ @type = type
14
+
15
+ @options = {}
16
+ options[:type] = I18n.translate("#{Base.i18n_scope}.types.#{filter.class.slug}") if type == :invalid_type
17
+ end
18
+
19
+ # The filter the error occured on.
20
+ #
21
+ # @return [ActiveInteraction::Filter]
22
+ attr_reader :filter
23
+
24
+ # The name of the error.
25
+ #
26
+ # @return [Symbol]
27
+ attr_reader :name
28
+
29
+ # Options passed to the error for error message creation.
30
+ #
31
+ # @return [Hash]
32
+ attr_reader :options
33
+
34
+ # The type of error.
35
+ #
36
+ # @return [Symbol]
37
+ attr_reader :type
38
+ end
39
+ end
40
+ end
@@ -73,38 +73,24 @@ module ActiveInteraction
73
73
  instance_eval(&block) if block_given?
74
74
  end
75
75
 
76
- # Convert a value into the expected type. If no value is given, fall back
77
- # to the default value.
76
+ # Processes the input through the filter and returns a variety of data
77
+ # about the input.
78
78
  #
79
79
  # @example
80
- # ActiveInteraction::Filter.new(:example).clean(nil, nil)
81
- # # => ActiveInteraction::MissingValueError: example
82
- # @example
83
- # ActiveInteraction::Filter.new(:example).clean(0, nil)
84
- # # => ActiveInteraction::InvalidValueError: example: 0
85
- # @example
86
- # ActiveInteraction::Filter.new(:example, default: nil).clean(nil, nil)
80
+ # input = ActiveInteraction::Filter.new(:example, default: nil).process(nil, nil)
81
+ # input.value
87
82
  # # => nil
88
- # @example
89
- # ActiveInteraction::Filter.new(:example, default: 0).clean(nil, nil)
90
- # # => ActiveInteraction::InvalidDefaultError: example: 0
91
83
  #
92
84
  # @param value [Object]
93
85
  # @param context [Base, nil]
94
86
  #
95
- # @return [Object]
87
+ # @return [Input, ArrayInput, HashInput]
96
88
  #
97
- # @raise [MissingValueError] If the value is missing and there is no
98
- # default.
99
- # @raise [InvalidValueError] If the value is invalid.
100
89
  # @raise (see #default)
101
- def clean(value, context)
102
- value = cast(value, context)
103
- if value.nil?
104
- default(context)
105
- else
106
- value
107
- end
90
+ def process(value, context)
91
+ value, error = cast(value, context)
92
+
93
+ Input.new(self, value: value, error: error)
108
94
  end
109
95
 
110
96
  # Get the default value.
@@ -126,16 +112,24 @@ module ActiveInteraction
126
112
  # @raise [NoDefaultError] If the default is missing.
127
113
  # @raise [InvalidDefaultError] If the default is invalid.
128
114
  def default(context = nil)
115
+ return @default if defined?(@default)
116
+
129
117
  raise NoDefaultError, name unless default?
130
118
 
131
119
  value = raw_default(context)
132
- raise InvalidValueError if value.is_a?(GroupedInput)
133
-
134
- cast(value, context)
135
- rescue InvalidNestedValueError => e
136
- raise InvalidDefaultError, "#{name}: #{value.inspect} (#{e})"
137
- rescue InvalidValueError, MissingValueError
138
- raise InvalidDefaultError, "#{name}: #{value.inspect}"
120
+ raise InvalidDefaultError, "#{name}: #{value.inspect}" if value.is_a?(GroupedInput)
121
+
122
+ @default =
123
+ if value.nil?
124
+ nil
125
+ else
126
+ default = process(value, context)
127
+ if default.errors.any? && default.errors.first.is_a?(Filter::Error)
128
+ raise InvalidDefaultError, "#{name}: #{value.inspect}"
129
+ end
130
+
131
+ default.value
132
+ end
139
133
  end
140
134
 
141
135
  # Get the description.
@@ -195,30 +189,26 @@ module ActiveInteraction
195
189
 
196
190
  private
197
191
 
198
- # rubocop:disable Metrics/MethodLength
199
- def cast(value, context, convert: true, reconstantize: true)
192
+ # rubocop:disable Metrics/PerceivedComplexity
193
+ def cast(value, context, convertize: true, reconstantize: true)
200
194
  if matches?(value)
201
- adjust_output(value, context)
202
- # we can't use `nil?` because BasicObject doesn't have it
203
- elsif value == nil # rubocop:disable Style/NilComparison
204
- raise MissingValueError, name unless default?
205
-
206
- nil
195
+ [adjust_output(value, context), nil]
196
+ elsif value == nil # rubocop:disable Style/NilComparison - BasicObject does not have `nil?`
197
+ default? ? [default(context), nil] : [value, Filter::Error.new(self, :missing)]
207
198
  elsif reconstantize
208
- send(__method__, value, context,
209
- convert: convert,
210
- reconstantize: false
211
- )
212
- elsif convert
213
- send(__method__, convert(value), context,
214
- convert: false,
215
- reconstantize: reconstantize
216
- )
199
+ send(__method__, value, context, convertize: convertize, reconstantize: false)
200
+ elsif convertize
201
+ value, error = convert(value)
202
+ if error
203
+ [value, error]
204
+ else
205
+ send(__method__, value, context, convertize: false, reconstantize: reconstantize)
206
+ end
217
207
  else
218
- raise InvalidValueError, "#{name}: #{describe(value)}"
208
+ [value, Filter::Error.new(self, :invalid_type)]
219
209
  end
220
210
  end
221
- # rubocop:enable Metrics/MethodLength
211
+ # rubocop:enable Metrics/PerceivedComplexity
222
212
 
223
213
  def matches?(_value)
224
214
  false
@@ -229,7 +219,7 @@ module ActiveInteraction
229
219
  end
230
220
 
231
221
  def convert(value)
232
- value
222
+ [value, nil]
233
223
  end
234
224
 
235
225
  def klass
@@ -246,9 +236,10 @@ module ActiveInteraction
246
236
  value = options.fetch(:default)
247
237
  return value unless value.is_a?(Proc)
248
238
 
249
- case value.arity
250
- when 1 then context.instance_exec(self, &value)
251
- else context.instance_exec(&value)
239
+ if value.arity == 1
240
+ context.instance_exec(self, &value)
241
+ else
242
+ context.instance_exec(&value)
252
243
  end
253
244
  end
254
245
  end
@@ -31,14 +31,16 @@ module ActiveInteraction
31
31
  def convert(value)
32
32
  if value.respond_to?(:to_str)
33
33
  value = value.to_str
34
- value.blank? ? send(__method__, nil) : convert_string(value)
34
+ if value.blank?
35
+ send(__method__, nil)
36
+ else
37
+ [convert_string(value), nil]
38
+ end
35
39
  elsif value.is_a?(GroupedInput)
36
- convert_grouped_input(value)
40
+ [convert_grouped_input(value), nil]
37
41
  else
38
42
  super
39
43
  end
40
- rescue ArgumentError
41
- value
42
44
  rescue NoMethodError # BasicObject
43
45
  super
44
46
  end
@@ -47,9 +49,10 @@ module ActiveInteraction
47
49
  if format?
48
50
  klass.strptime(value, format)
49
51
  else
50
- klass.parse(value) ||
51
- (raise ArgumentError, "no time information in #{value.inspect}")
52
+ klass.parse(value) || value
52
53
  end
54
+ rescue ArgumentError
55
+ value
53
56
  end
54
57
 
55
58
  def convert_grouped_input(value)
@@ -21,12 +21,16 @@ module ActiveInteraction
21
21
 
22
22
  def convert(value)
23
23
  if value.is_a?(Numeric)
24
- safe_converter(value)
24
+ [safe_converter(value), nil]
25
25
  elsif value.respond_to?(:to_int)
26
- safe_converter(value.to_int)
26
+ [safe_converter(value.to_int), nil]
27
27
  elsif value.respond_to?(:to_str)
28
28
  value = value.to_str
29
- value.blank? ? send(__method__, nil) : safe_converter(value)
29
+ if value.blank?
30
+ send(__method__, nil)
31
+ else
32
+ [safe_converter(value), nil]
33
+ end
30
34
  else
31
35
  super
32
36
  end