active_interaction 4.1.0 → 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 +128 -1
- data/README.md +63 -28
- 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 +34 -6
- 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 -16
- 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 +40 -91
- data/lib/active_interaction/filter_column.rb +0 -57
@@ -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
|
@@ -8,6 +8,8 @@ module ActiveInteraction
|
|
8
8
|
#
|
9
9
|
# @!macro filter_method_params
|
10
10
|
# @param block [Proc] filter method to apply to each element
|
11
|
+
# @option options [Boolean] :index_errors (ActiveRecord.index_nested_attribute_errors) returns errors with an
|
12
|
+
# index
|
11
13
|
#
|
12
14
|
# @example
|
13
15
|
# array :ids
|
@@ -36,8 +38,37 @@ module ActiveInteraction
|
|
36
38
|
|
37
39
|
register :array
|
38
40
|
|
41
|
+
def process(value, context)
|
42
|
+
input = super
|
43
|
+
|
44
|
+
return ArrayInput.new(self, value: input.value, error: input.errors.first) if input.errors.any?
|
45
|
+
return ArrayInput.new(self, value: default(context), error: input.errors.first) if input.value.nil?
|
46
|
+
|
47
|
+
value = input.value
|
48
|
+
error = nil
|
49
|
+
children = []
|
50
|
+
|
51
|
+
unless filters.empty?
|
52
|
+
value.each do |item|
|
53
|
+
children.push(filters[:'0'].process(item, context))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ArrayInput.new(self, value: value, error: error, children: children, index_errors: index_errors?)
|
58
|
+
end
|
59
|
+
|
39
60
|
private
|
40
61
|
|
62
|
+
def index_errors?
|
63
|
+
default =
|
64
|
+
if ::ActiveRecord.respond_to?(:index_nested_attribute_errors)
|
65
|
+
::ActiveRecord.index_nested_attribute_errors # Moved to here in Rails 7.0
|
66
|
+
else
|
67
|
+
::ActiveRecord::Base.index_nested_attribute_errors
|
68
|
+
end
|
69
|
+
options.fetch(:index_errors, default)
|
70
|
+
end
|
71
|
+
|
41
72
|
def klasses
|
42
73
|
%w[
|
43
74
|
ActiveRecord::Relation
|
@@ -55,16 +86,13 @@ module ActiveInteraction
|
|
55
86
|
false
|
56
87
|
end
|
57
88
|
|
58
|
-
def adjust_output(value,
|
59
|
-
|
60
|
-
|
61
|
-
filter = filters.values.first
|
62
|
-
value.map { |e| filter.clean(e, context) }
|
89
|
+
def adjust_output(value, _context)
|
90
|
+
value.to_a
|
63
91
|
end
|
64
92
|
|
65
93
|
def convert(value)
|
66
94
|
if value.respond_to?(:to_ary)
|
67
|
-
value.to_ary
|
95
|
+
[value.to_ary, nil]
|
68
96
|
else
|
69
97
|
super
|
70
98
|
end
|
@@ -6,7 +6,8 @@ module ActiveInteraction
|
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
7
7
|
# the attributes are Booleans. The strings `"1"`, `"true"`, and `"on"`
|
8
8
|
# (case-insensitive) are converted to `true` while the strings `"0"`,
|
9
|
-
# `"false"`, and `"off"` are converted to `false`.
|
9
|
+
# `"false"`, and `"off"` are converted to `false`. Blank strings are
|
10
|
+
# treated as a `nil` value.
|
10
11
|
#
|
11
12
|
# @!macro filter_method_params
|
12
13
|
#
|
@@ -38,9 +39,9 @@ module ActiveInteraction
|
|
38
39
|
|
39
40
|
case value
|
40
41
|
when /\A(?:0|false|off)\z/i
|
41
|
-
false
|
42
|
+
[false, nil]
|
42
43
|
when /\A(?:1|true|on)\z/i
|
43
|
-
true
|
44
|
+
[true, nil]
|
44
45
|
else
|
45
46
|
super
|
46
47
|
end
|
@@ -6,7 +6,7 @@ module ActiveInteraction
|
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
7
7
|
# the attributes are Dates. String values are processed using `parse`
|
8
8
|
# unless the format option is given, in which case they will be
|
9
|
-
# processed with `strptime`.
|
9
|
+
# processed with `strptime`. Blank strings are treated as a `nil` value.
|
10
10
|
#
|
11
11
|
# @!macro filter_method_params
|
12
12
|
# @option options [String] :format parse strings using this format string
|
@@ -6,7 +6,7 @@ module ActiveInteraction
|
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
7
7
|
# the attributes are DateTimes. String values are processed using
|
8
8
|
# `parse` unless the format option is given, in which case they will be
|
9
|
-
# processed with `strptime`.
|
9
|
+
# processed with `strptime`. Blank strings are treated as a `nil` value.
|
10
10
|
#
|
11
11
|
# @!macro filter_method_params
|
12
12
|
# @option options [String] :format parse strings using this format string
|
@@ -7,7 +7,7 @@ module ActiveInteraction
|
|
7
7
|
# @!method self.decimal(*attributes, options = {})
|
8
8
|
# Creates accessors for the attributes and ensures that values passed to
|
9
9
|
# the attributes are BigDecimals. Numerics and String values are
|
10
|
-
# converted into BigDecimals.
|
10
|
+
# converted into BigDecimals. Blank strings are treated as a `nil` value.
|
11
11
|
#
|
12
12
|
# @!macro filter_method_params
|
13
13
|
#
|
@@ -5,7 +5,7 @@ module ActiveInteraction
|
|
5
5
|
# @!method self.float(*attributes, options = {})
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
7
7
|
# the attributes are Floats. Integer and String values are converted
|
8
|
-
# into Floats.
|
8
|
+
# into Floats. Blank strings are treated as a `nil` value.
|
9
9
|
#
|
10
10
|
# @!macro filter_method_params
|
11
11
|
#
|
@@ -25,6 +25,26 @@ module ActiveInteraction
|
|
25
25
|
|
26
26
|
register :hash
|
27
27
|
|
28
|
+
def process(value, context)
|
29
|
+
input = super
|
30
|
+
|
31
|
+
return HashInput.new(self, value: input.value, error: input.errors.first) if input.errors.first
|
32
|
+
return HashInput.new(self, value: default(context), error: input.errors.first) if input.value.nil?
|
33
|
+
|
34
|
+
value = strip? ? HashWithIndifferentAccess.new : input.value
|
35
|
+
error = nil
|
36
|
+
children = {}
|
37
|
+
|
38
|
+
filters.each do |name, filter|
|
39
|
+
filter.process(input.value[name], context).tap do |result|
|
40
|
+
value[name] = result.value
|
41
|
+
children[name.to_sym] = result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
HashInput.new(self, value: value, error: error, children: children)
|
46
|
+
end
|
47
|
+
|
28
48
|
private
|
29
49
|
|
30
50
|
def matches?(value)
|
@@ -33,29 +53,17 @@ module ActiveInteraction
|
|
33
53
|
false
|
34
54
|
end
|
35
55
|
|
36
|
-
def clean_value(hash, name, filter, value, context)
|
37
|
-
hash[name] = filter.clean(value[name], context)
|
38
|
-
rescue InvalidValueError, MissingValueError
|
39
|
-
raise InvalidNestedValueError.new(name, value[name])
|
40
|
-
end
|
41
|
-
|
42
56
|
def strip?
|
43
57
|
options.fetch(:strip, true)
|
44
58
|
end
|
45
59
|
|
46
|
-
def adjust_output(value,
|
47
|
-
|
48
|
-
|
49
|
-
initial = strip? ? ActiveSupport::HashWithIndifferentAccess.new : value
|
50
|
-
|
51
|
-
filters.each_with_object(initial) do |(name, filter), hash|
|
52
|
-
clean_value(hash, name.to_s, filter, value, context)
|
53
|
-
end
|
60
|
+
def adjust_output(value, _context)
|
61
|
+
ActiveSupport::HashWithIndifferentAccess.new(value)
|
54
62
|
end
|
55
63
|
|
56
64
|
def convert(value)
|
57
65
|
if value.respond_to?(:to_hash)
|
58
|
-
value.to_hash
|
66
|
+
[value.to_hash, nil]
|
59
67
|
else
|
60
68
|
super
|
61
69
|
end
|
@@ -5,7 +5,7 @@ module ActiveInteraction
|
|
5
5
|
# @!method self.integer(*attributes, options = {})
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
7
7
|
# the attributes are Integers. String values are converted into
|
8
|
-
# Integers.
|
8
|
+
# Integers. Blank strings are treated as a `nil` value.
|
9
9
|
#
|
10
10
|
# @!macro filter_method_params
|
11
11
|
# @option options [Integer] :base (10) The base used to convert strings
|