active_interaction 4.1.0 → 5.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +150 -1
- data/CONTRIBUTING.md +11 -3
- data/README.md +256 -215
- 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 +40 -52
- 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 +40 -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 +161 -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 +41 -36
- data/spec/active_interaction/array_input_spec.rb +166 -0
- data/spec/active_interaction/base_spec.rb +34 -248
- 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 +27 -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 +109 -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 +170 -18
- data/spec/active_interaction/integration/array_interaction_spec.rb +3 -7
- data/spec/active_interaction/integration/record_integration_spec.rb +5 -0
- data/spec/active_interaction/modules/validation_spec.rb +8 -31
- data/spec/spec_helper.rb +9 -0
- data/spec/support/concerns.rb +2 -2
- data/spec/support/filters.rb +27 -51
- data/spec/support/interactions.rb +4 -4
- metadata +43 -44
- data/lib/active_interaction/filter_column.rb +0 -57
@@ -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,43 @@ 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.map! do |item|
|
53
|
+
result = filters[:'0'].process(item, context)
|
54
|
+
children.push(result)
|
55
|
+
result.value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
ArrayInput.new(self, value: value, error: error, children: children, index_errors: index_errors?)
|
60
|
+
end
|
61
|
+
|
39
62
|
private
|
40
63
|
|
64
|
+
def index_errors?
|
65
|
+
klass = 'ActiveRecord'.safe_constantize
|
66
|
+
|
67
|
+
default =
|
68
|
+
if !klass
|
69
|
+
false
|
70
|
+
elsif klass.respond_to?(:index_nested_attribute_errors)
|
71
|
+
klass.index_nested_attribute_errors # Moved to here in Rails 7.0
|
72
|
+
else
|
73
|
+
klass::Base.index_nested_attribute_errors
|
74
|
+
end
|
75
|
+
options.fetch(:index_errors, default)
|
76
|
+
end
|
77
|
+
|
41
78
|
def klasses
|
42
79
|
%w[
|
43
80
|
ActiveRecord::Relation
|
@@ -55,16 +92,13 @@ module ActiveInteraction
|
|
55
92
|
false
|
56
93
|
end
|
57
94
|
|
58
|
-
def adjust_output(value,
|
59
|
-
|
60
|
-
|
61
|
-
filter = filters.values.first
|
62
|
-
value.map { |e| filter.clean(e, context) }
|
95
|
+
def adjust_output(value, _context)
|
96
|
+
value.to_a
|
63
97
|
end
|
64
98
|
|
65
99
|
def convert(value)
|
66
100
|
if value.respond_to?(:to_ary)
|
67
|
-
value.to_ary
|
101
|
+
[value.to_ary, nil]
|
68
102
|
else
|
69
103
|
super
|
70
104
|
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
|
@@ -47,34 +47,34 @@ module ActiveInteraction
|
|
47
47
|
"constant #{const_name.inspect} does not exist"
|
48
48
|
end
|
49
49
|
|
50
|
-
def matches?(
|
51
|
-
return false if
|
52
|
-
return matches_methods?(
|
50
|
+
def matches?(value)
|
51
|
+
return false if value == nil # rubocop:disable Style/NilComparison
|
52
|
+
return matches_methods?(value) if options.key?(:methods)
|
53
53
|
|
54
54
|
const = from
|
55
|
-
if checking_class_inheritance?(
|
56
|
-
class_inherits_from?(
|
55
|
+
if checking_class_inheritance?(value, const)
|
56
|
+
class_inherits_from?(value, const)
|
57
57
|
else
|
58
|
-
singleton_ancestor?(
|
58
|
+
singleton_ancestor?(value, const)
|
59
59
|
end
|
60
60
|
rescue NoMethodError
|
61
61
|
false
|
62
62
|
end
|
63
63
|
|
64
|
-
def matches_methods?(
|
65
|
-
options[:methods].all? { |method|
|
64
|
+
def matches_methods?(value)
|
65
|
+
options[:methods].all? { |method| value.respond_to?(method) }
|
66
66
|
end
|
67
67
|
|
68
|
-
def checking_class_inheritance?(
|
69
|
-
|
68
|
+
def checking_class_inheritance?(value, from)
|
69
|
+
value.is_a?(Class) && from.is_a?(Class)
|
70
70
|
end
|
71
71
|
|
72
72
|
def class_inherits_from?(klass, inherits_from)
|
73
73
|
klass != inherits_from && klass.ancestors.include?(inherits_from)
|
74
74
|
end
|
75
75
|
|
76
|
-
def singleton_ancestor?(
|
77
|
-
|
76
|
+
def singleton_ancestor?(value, from)
|
77
|
+
value.class != from && value.singleton_class.ancestors.include?(from)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
@@ -38,19 +38,25 @@ module ActiveInteraction
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def matches?(value)
|
41
|
+
return false if value == nil # rubocop:disable Style/NilComparison
|
42
|
+
|
41
43
|
value.class <= klass
|
42
44
|
rescue NoMethodError
|
43
45
|
false
|
44
46
|
end
|
45
47
|
|
46
48
|
def convert(value)
|
47
|
-
converter(value)
|
48
|
-
|
49
|
+
result = converter(value)
|
50
|
+
|
51
|
+
if result.nil?
|
52
|
+
[value, Filter::Error.new(self, :invalid_type)]
|
53
|
+
else
|
54
|
+
[result, nil]
|
49
55
|
end
|
50
56
|
rescue StandardError => e
|
51
57
|
raise e if e.is_a?(InvalidConverterError)
|
52
58
|
|
53
|
-
|
59
|
+
[value, Filter::Error.new(self, :invalid_type)]
|
54
60
|
end
|
55
61
|
|
56
62
|
def converter(value)
|
@@ -4,7 +4,9 @@ module ActiveInteraction
|
|
4
4
|
class Base # rubocop:disable Lint/EmptyClass
|
5
5
|
# @!method self.record(*attributes, options = {})
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
7
|
-
# the attributes are the correct class.
|
7
|
+
# the attributes are the correct class. Blank strings passed in will be
|
8
|
+
# treated as `nil` and the `finder` will not be called.
|
9
|
+
|
8
10
|
#
|
9
11
|
# @!macro filter_method_params
|
10
12
|
# @option options [Class, String, Symbol] :class (use the attribute name)
|
@@ -44,20 +46,28 @@ module ActiveInteraction
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def convert(value)
|
47
|
-
|
48
|
-
find(klass, value, finder)
|
49
|
-
end
|
49
|
+
return [nil, nil] if blank_string?(value)
|
50
50
|
|
51
|
-
|
52
|
-
result = klass
|
51
|
+
finder = options.fetch(:finder, :find)
|
52
|
+
result = find(klass, value, finder)
|
53
53
|
|
54
|
-
|
54
|
+
if result.nil?
|
55
|
+
[value, Filter::Error.new(self, :invalid_type)]
|
56
|
+
else
|
57
|
+
[result, nil]
|
58
|
+
end
|
59
|
+
end
|
55
60
|
|
56
|
-
|
57
|
-
|
58
|
-
|
61
|
+
def blank_string?(value)
|
62
|
+
value.is_a?(String) && value.blank?
|
63
|
+
rescue NoMethodError # BasicObject
|
64
|
+
false
|
65
|
+
end
|
59
66
|
|
60
|
-
|
67
|
+
def find(klass, value, finder)
|
68
|
+
klass.public_send(finder, value)
|
69
|
+
rescue StandardError
|
70
|
+
nil
|
61
71
|
end
|
62
72
|
end
|
63
73
|
end
|
@@ -6,9 +6,9 @@ module ActiveInteraction
|
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
7
7
|
# the attributes are Times. Numeric values are processed using `at`.
|
8
8
|
# Strings are processed using `parse` unless the format option is
|
9
|
-
# given, in which case they will be processed with `strptime`.
|
10
|
-
# `Time.zone` is available it
|
11
|
-
# zone aware.
|
9
|
+
# given, in which case they will be processed with `strptime`. Blank
|
10
|
+
# strings are treated as a `nil` value. If `Time.zone` is available it
|
11
|
+
# will be used so that the values are time zone aware.
|
12
12
|
#
|
13
13
|
# @!macro filter_method_params
|
14
14
|
# @option options [String] :format parse strings using this format string
|
@@ -59,7 +59,7 @@ module ActiveInteraction
|
|
59
59
|
value = value.to_int if value.respond_to?(:to_int)
|
60
60
|
|
61
61
|
if value.is_a?(Numeric)
|
62
|
-
klass.at(value)
|
62
|
+
[klass.at(value), nil]
|
63
63
|
else
|
64
64
|
super
|
65
65
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveInteraction
|
4
|
+
# Represents a processed hash input.
|
5
|
+
class HashInput < Input
|
6
|
+
# @private
|
7
|
+
def initialize(filter, value: nil, error: nil, children: {})
|
8
|
+
super(filter, value: value, error: error)
|
9
|
+
|
10
|
+
@children = children
|
11
|
+
end
|
12
|
+
|
13
|
+
# @overload children
|
14
|
+
# Child inputs if nested filters are used.
|
15
|
+
#
|
16
|
+
# @return [Hash{ Symbol => Input, ArrayInput, HashInput }]
|
17
|
+
attr_reader :children
|
18
|
+
|
19
|
+
# Any errors that occurred during processing.
|
20
|
+
#
|
21
|
+
# @return [Filter::Error]
|
22
|
+
def errors
|
23
|
+
return @errors if defined?(@errors)
|
24
|
+
|
25
|
+
return @errors = super if @error
|
26
|
+
|
27
|
+
child_errors = get_errors(children)
|
28
|
+
|
29
|
+
return @errors = super if child_errors.empty?
|
30
|
+
|
31
|
+
@errors ||=
|
32
|
+
child_errors.map do |error|
|
33
|
+
Filter::Error.new(error.filter, error.type, name: :"#{@filter.name}.#{error.name}")
|
34
|
+
end.freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def get_errors(children)
|
40
|
+
children.values.flat_map(&:errors)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveInteraction
|
4
|
+
# Represents a processed input.
|
5
|
+
class Input
|
6
|
+
# @private
|
7
|
+
def initialize(filter, value: nil, error: nil)
|
8
|
+
@filter = filter
|
9
|
+
@value = value
|
10
|
+
@error = error
|
11
|
+
end
|
12
|
+
|
13
|
+
# The processed input value.
|
14
|
+
attr_reader :value
|
15
|
+
|
16
|
+
# Any errors that occurred during processing.
|
17
|
+
#
|
18
|
+
# @return [Filter::Error]
|
19
|
+
def errors
|
20
|
+
@errors ||= Array(@error)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|