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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -6
- data/README.md +67 -32
- data/lib/active_interaction/array_input.rb +77 -0
- data/lib/active_interaction/base.rb +14 -98
- data/lib/active_interaction/concerns/active_recordable.rb +3 -3
- data/lib/active_interaction/concerns/missable.rb +2 -2
- data/lib/active_interaction/errors.rb +6 -88
- data/lib/active_interaction/exceptions.rb +47 -0
- data/lib/active_interaction/filter/column.rb +59 -0
- data/lib/active_interaction/filter/error.rb +40 -0
- data/lib/active_interaction/filter.rb +44 -53
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +9 -6
- data/lib/active_interaction/filters/abstract_numeric_filter.rb +7 -3
- data/lib/active_interaction/filters/array_filter.rb +36 -10
- data/lib/active_interaction/filters/boolean_filter.rb +4 -3
- data/lib/active_interaction/filters/date_filter.rb +1 -1
- data/lib/active_interaction/filters/date_time_filter.rb +1 -1
- data/lib/active_interaction/filters/decimal_filter.rb +1 -1
- data/lib/active_interaction/filters/float_filter.rb +1 -1
- data/lib/active_interaction/filters/hash_filter.rb +23 -15
- data/lib/active_interaction/filters/integer_filter.rb +1 -1
- data/lib/active_interaction/filters/interface_filter.rb +12 -12
- data/lib/active_interaction/filters/object_filter.rb +9 -3
- data/lib/active_interaction/filters/record_filter.rb +21 -11
- data/lib/active_interaction/filters/string_filter.rb +1 -1
- data/lib/active_interaction/filters/symbol_filter.rb +1 -1
- data/lib/active_interaction/filters/time_filter.rb +4 -4
- data/lib/active_interaction/hash_input.rb +43 -0
- data/lib/active_interaction/input.rb +23 -0
- data/lib/active_interaction/inputs.rb +157 -46
- data/lib/active_interaction/locale/en.yml +0 -1
- data/lib/active_interaction/locale/fr.yml +0 -1
- data/lib/active_interaction/locale/it.yml +0 -1
- data/lib/active_interaction/locale/ja.yml +0 -1
- data/lib/active_interaction/locale/pt-BR.yml +0 -1
- data/lib/active_interaction/modules/validation.rb +6 -17
- data/lib/active_interaction/version.rb +1 -1
- data/lib/active_interaction.rb +43 -36
- data/spec/active_interaction/array_input_spec.rb +166 -0
- data/spec/active_interaction/base_spec.rb +15 -240
- data/spec/active_interaction/concerns/active_modelable_spec.rb +3 -3
- data/spec/active_interaction/concerns/active_recordable_spec.rb +7 -7
- data/spec/active_interaction/concerns/hashable_spec.rb +8 -8
- data/spec/active_interaction/concerns/missable_spec.rb +9 -9
- data/spec/active_interaction/concerns/runnable_spec.rb +34 -32
- data/spec/active_interaction/errors_spec.rb +60 -43
- data/spec/active_interaction/{filter_column_spec.rb → filter/column_spec.rb} +3 -10
- data/spec/active_interaction/filter_spec.rb +6 -6
- data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +2 -2
- data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +2 -2
- data/spec/active_interaction/filters/array_filter_spec.rb +99 -24
- data/spec/active_interaction/filters/boolean_filter_spec.rb +12 -11
- data/spec/active_interaction/filters/date_filter_spec.rb +32 -27
- data/spec/active_interaction/filters/date_time_filter_spec.rb +34 -29
- data/spec/active_interaction/filters/decimal_filter_spec.rb +20 -18
- data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
- data/spec/active_interaction/filters/float_filter_spec.rb +19 -17
- data/spec/active_interaction/filters/hash_filter_spec.rb +16 -18
- data/spec/active_interaction/filters/integer_filter_spec.rb +24 -22
- data/spec/active_interaction/filters/interface_filter_spec.rb +105 -82
- data/spec/active_interaction/filters/object_filter_spec.rb +52 -36
- data/spec/active_interaction/filters/record_filter_spec.rb +61 -39
- data/spec/active_interaction/filters/string_filter_spec.rb +7 -7
- data/spec/active_interaction/filters/symbol_filter_spec.rb +6 -6
- data/spec/active_interaction/filters/time_filter_spec.rb +57 -34
- data/spec/active_interaction/hash_input_spec.rb +58 -0
- data/spec/active_interaction/i18n_spec.rb +22 -17
- data/spec/active_interaction/inputs_spec.rb +167 -23
- data/spec/active_interaction/integration/array_interaction_spec.rb +3 -7
- data/spec/active_interaction/modules/validation_spec.rb +8 -31
- data/spec/spec_helper.rb +8 -0
- data/spec/support/concerns.rb +2 -2
- data/spec/support/filters.rb +27 -51
- data/spec/support/interactions.rb +4 -4
- metadata +45 -95
- 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 :
|
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
|
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
|
-
|
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
|
-
|
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 [
|
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
|
-
#
|
195
|
-
|
196
|
-
|
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
|
-
|
203
|
+
filter
|
268
204
|
|
269
205
|
super if errors.empty?
|
270
206
|
end
|
271
207
|
|
272
208
|
private
|
273
209
|
|
274
|
-
def
|
275
|
-
|
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::
|
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 [
|
25
|
+
# @return [Filter::Column, nil]
|
26
26
|
def column_for_attribute(name)
|
27
27
|
filter = self.class.filters[name]
|
28
|
-
|
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
|
-
#
|
77
|
-
#
|
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).
|
81
|
-
#
|
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 [
|
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
|
102
|
-
value = cast(value, context)
|
103
|
-
|
104
|
-
|
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
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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/
|
199
|
-
def cast(value, context,
|
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
|
-
|
203
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
208
|
+
[value, Filter::Error.new(self, :invalid_type)]
|
219
209
|
end
|
220
210
|
end
|
221
|
-
# rubocop:enable Metrics/
|
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
|
-
|
250
|
-
|
251
|
-
else
|
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?
|
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?
|
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
|