active_interaction 3.8.2 → 4.0.3
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 +192 -0
- data/README.md +93 -107
- data/lib/active_interaction.rb +1 -7
- data/lib/active_interaction/base.rb +44 -67
- data/lib/active_interaction/concerns/active_modelable.rb +1 -3
- data/lib/active_interaction/concerns/active_recordable.rb +1 -6
- data/lib/active_interaction/concerns/hashable.rb +0 -1
- data/lib/active_interaction/concerns/missable.rb +0 -1
- data/lib/active_interaction/concerns/runnable.rb +7 -14
- data/lib/active_interaction/errors.rb +4 -19
- data/lib/active_interaction/filter.rb +66 -37
- data/lib/active_interaction/filter_column.rb +0 -3
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +38 -36
- data/lib/active_interaction/filters/abstract_numeric_filter.rb +27 -17
- data/lib/active_interaction/filters/array_filter.rb +59 -36
- data/lib/active_interaction/filters/boolean_filter.rb +26 -12
- data/lib/active_interaction/filters/date_filter.rb +1 -2
- data/lib/active_interaction/filters/date_time_filter.rb +1 -2
- data/lib/active_interaction/filters/decimal_filter.rb +10 -28
- data/lib/active_interaction/filters/file_filter.rb +6 -5
- data/lib/active_interaction/filters/float_filter.rb +1 -2
- data/lib/active_interaction/filters/hash_filter.rb +37 -27
- data/lib/active_interaction/filters/integer_filter.rb +7 -8
- data/lib/active_interaction/filters/interface_filter.rb +48 -14
- data/lib/active_interaction/filters/object_filter.rb +23 -50
- data/lib/active_interaction/filters/record_filter.rb +10 -35
- data/lib/active_interaction/filters/string_filter.rb +21 -12
- data/lib/active_interaction/filters/symbol_filter.rb +13 -7
- data/lib/active_interaction/filters/time_filter.rb +24 -19
- data/lib/active_interaction/grouped_input.rb +0 -3
- data/lib/active_interaction/inputs.rb +120 -0
- data/lib/active_interaction/modules/validation.rb +9 -12
- data/lib/active_interaction/version.rb +1 -3
- data/spec/active_interaction/base_spec.rb +38 -99
- data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -2
- data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -2
- data/spec/active_interaction/concerns/hashable_spec.rb +0 -2
- data/spec/active_interaction/concerns/missable_spec.rb +0 -2
- data/spec/active_interaction/concerns/runnable_spec.rb +26 -12
- data/spec/active_interaction/errors_spec.rb +4 -25
- data/spec/active_interaction/filter_column_spec.rb +0 -2
- data/spec/active_interaction/filter_spec.rb +0 -2
- data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +1 -3
- data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -3
- data/spec/active_interaction/filters/array_filter_spec.rb +51 -14
- data/spec/active_interaction/filters/boolean_filter_spec.rb +58 -4
- data/spec/active_interaction/filters/date_filter_spec.rb +43 -3
- data/spec/active_interaction/filters/date_time_filter_spec.rb +43 -3
- data/spec/active_interaction/filters/decimal_filter_spec.rb +57 -3
- data/spec/active_interaction/filters/file_filter_spec.rb +1 -3
- data/spec/active_interaction/filters/float_filter_spec.rb +60 -4
- data/spec/active_interaction/filters/hash_filter_spec.rb +19 -9
- data/spec/active_interaction/filters/integer_filter_spec.rb +49 -7
- data/spec/active_interaction/filters/interface_filter_spec.rb +397 -24
- data/spec/active_interaction/filters/object_filter_spec.rb +23 -59
- data/spec/active_interaction/filters/record_filter_spec.rb +23 -49
- data/spec/active_interaction/filters/string_filter_spec.rb +15 -3
- data/spec/active_interaction/filters/symbol_filter_spec.rb +15 -3
- data/spec/active_interaction/filters/time_filter_spec.rb +65 -3
- data/spec/active_interaction/grouped_input_spec.rb +0 -2
- data/spec/active_interaction/i18n_spec.rb +3 -7
- data/spec/active_interaction/{modules/input_processor_spec.rb → inputs_spec.rb} +35 -5
- data/spec/active_interaction/integration/array_interaction_spec.rb +18 -22
- data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/date_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/file_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/float_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/hash_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/interface_interaction_spec.rb +1 -3
- data/spec/active_interaction/integration/object_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/string_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -2
- data/spec/active_interaction/integration/time_interaction_spec.rb +14 -18
- data/spec/active_interaction/modules/validation_spec.rb +1 -3
- data/spec/spec_helper.rb +2 -6
- data/spec/support/concerns.rb +0 -2
- data/spec/support/filters.rb +13 -9
- data/spec/support/interactions.rb +22 -14
- metadata +81 -57
- data/lib/active_interaction/backports.rb +0 -59
- data/lib/active_interaction/filters/abstract_filter.rb +0 -19
- data/lib/active_interaction/modules/input_processor.rb +0 -52
- data/spec/active_interaction/filters/abstract_filter_spec.rb +0 -8
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module ActiveInteraction
|
@@ -8,61 +7,64 @@ module ActiveInteraction
|
|
8
7
|
# objects.
|
9
8
|
#
|
10
9
|
# @private
|
11
|
-
class AbstractDateTimeFilter <
|
12
|
-
alias _cast cast
|
13
|
-
private :_cast
|
14
|
-
|
15
|
-
def cast(value, context)
|
16
|
-
case value
|
17
|
-
when String
|
18
|
-
convert(value, context)
|
19
|
-
when GroupedInput
|
20
|
-
convert(stringify(value), context)
|
21
|
-
when *klasses
|
22
|
-
value
|
23
|
-
else
|
24
|
-
super
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
10
|
+
class AbstractDateTimeFilter < Filter
|
28
11
|
def database_column_type
|
29
12
|
self.class.slug
|
30
13
|
end
|
31
14
|
|
15
|
+
def accepts_grouped_inputs?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
32
19
|
private
|
33
20
|
|
34
|
-
def
|
21
|
+
def klasses
|
22
|
+
[klass]
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches?(value)
|
26
|
+
klasses.any? { |klass| value.is_a?(klass) }
|
27
|
+
rescue NoMethodError # BasicObject
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert(value)
|
32
|
+
if value.respond_to?(:to_str)
|
33
|
+
value = value.to_str
|
34
|
+
value.blank? ? send(__method__, nil) : convert_string(value)
|
35
|
+
elsif value.is_a?(GroupedInput)
|
36
|
+
convert_grouped_input(value)
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
rescue ArgumentError
|
41
|
+
value
|
42
|
+
rescue NoMethodError # BasicObject
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def convert_string(value)
|
35
47
|
if format?
|
36
48
|
klass.strptime(value, format)
|
37
49
|
else
|
38
50
|
klass.parse(value) ||
|
39
51
|
(raise ArgumentError, "no time information in #{value.inspect}")
|
40
52
|
end
|
41
|
-
rescue ArgumentError
|
42
|
-
_cast(value, context)
|
43
53
|
end
|
44
54
|
|
45
|
-
|
55
|
+
def convert_grouped_input(value)
|
56
|
+
date = %w[1 2 3].map { |key| value[key] }.join('-')
|
57
|
+
time = %w[4 5 6].map { |key| value[key] }.join(':')
|
58
|
+
|
59
|
+
convert_string("#{date} #{time}")
|
60
|
+
end
|
61
|
+
|
46
62
|
def format
|
47
63
|
options.fetch(:format)
|
48
64
|
end
|
49
65
|
|
50
|
-
# @return [Boolean]
|
51
66
|
def format?
|
52
67
|
options.key?(:format)
|
53
68
|
end
|
54
|
-
|
55
|
-
# @return [Array<Class>]
|
56
|
-
def klasses
|
57
|
-
[klass]
|
58
|
-
end
|
59
|
-
|
60
|
-
# @return [String]
|
61
|
-
def stringify(value)
|
62
|
-
date = %w[1 2 3].map { |key| value[key] }.join('-')
|
63
|
-
time = %w[4 5 6].map { |key| value[key] }.join(':')
|
64
|
-
|
65
|
-
"#{date} #{time}"
|
66
|
-
end
|
67
69
|
end
|
68
70
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module ActiveInteraction
|
@@ -7,31 +6,42 @@ module ActiveInteraction
|
|
7
6
|
# Common logic for filters that handle numeric objects.
|
8
7
|
#
|
9
8
|
# @private
|
10
|
-
class AbstractNumericFilter <
|
11
|
-
|
12
|
-
|
9
|
+
class AbstractNumericFilter < Filter
|
10
|
+
def database_column_type
|
11
|
+
self.class.slug
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def matches?(value)
|
17
|
+
value.is_a?(klass)
|
18
|
+
rescue NoMethodError # BasicObject
|
19
|
+
false
|
20
|
+
end
|
13
21
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
def convert(value)
|
23
|
+
if value.is_a?(Numeric)
|
24
|
+
safe_converter(value)
|
25
|
+
elsif value.respond_to?(:to_int)
|
26
|
+
safe_converter(value.to_int)
|
27
|
+
elsif value.respond_to?(:to_str)
|
28
|
+
value = value.to_str
|
29
|
+
value.blank? ? send(__method__, nil) : safe_converter(value)
|
20
30
|
else
|
21
31
|
super
|
22
32
|
end
|
33
|
+
rescue NoMethodError # BasicObject
|
34
|
+
super
|
23
35
|
end
|
24
36
|
|
25
|
-
def
|
26
|
-
|
37
|
+
def converter(value)
|
38
|
+
Kernel.public_send(klass.name, value)
|
27
39
|
end
|
28
40
|
|
29
|
-
|
30
|
-
|
31
|
-
def convert(value, context)
|
32
|
-
Kernel.public_send(klass.name, value)
|
41
|
+
def safe_converter(value)
|
42
|
+
converter(value)
|
33
43
|
rescue ArgumentError
|
34
|
-
|
44
|
+
value
|
35
45
|
end
|
36
46
|
end
|
37
47
|
end
|
@@ -1,8 +1,7 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module ActiveInteraction
|
5
|
-
class Base
|
4
|
+
class Base # rubocop:disable Lint/EmptyClass
|
6
5
|
# @!method self.array(*attributes, options = {}, &block)
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Arrays.
|
@@ -26,63 +25,87 @@ module ActiveInteraction
|
|
26
25
|
class ArrayFilter < Filter
|
27
26
|
include Missable
|
28
27
|
|
28
|
+
# The array starts with the class override key and then contains any
|
29
|
+
# additional options which halt explicit setting of the class.
|
30
|
+
FILTER_NAME_OR_OPTION = {
|
31
|
+
'ActiveInteraction::ObjectFilter' => [:class].freeze,
|
32
|
+
'ActiveInteraction::RecordFilter' => [:class].freeze,
|
33
|
+
'ActiveInteraction::InterfaceFilter' => %i[from methods].freeze
|
34
|
+
}.freeze
|
35
|
+
private_constant :FILTER_NAME_OR_OPTION
|
36
|
+
|
29
37
|
register :array
|
30
38
|
|
31
|
-
|
32
|
-
case value
|
33
|
-
when *classes
|
34
|
-
return value if filters.empty?
|
39
|
+
private
|
35
40
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
def klasses
|
42
|
+
%w[
|
43
|
+
ActiveRecord::Relation
|
44
|
+
ActiveRecord::Associations::CollectionProxy
|
45
|
+
].each_with_object([Array]) do |name, result|
|
46
|
+
next unless (klass = name.safe_constantize)
|
47
|
+
|
48
|
+
result.push(klass)
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
52
|
+
def matches?(value)
|
53
|
+
klasses.any? { |klass| value.is_a?(klass) }
|
54
|
+
rescue NoMethodError # BasicObject
|
55
|
+
false
|
56
|
+
end
|
46
57
|
|
47
|
-
|
58
|
+
def adjust_output(value, context)
|
59
|
+
return value if filters.empty?
|
60
|
+
|
61
|
+
filter = filters.values.first
|
62
|
+
value.map { |e| filter.clean(e, context) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def convert(value)
|
66
|
+
if value.respond_to?(:to_ary)
|
67
|
+
value.to_ary
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
rescue NoMethodError # BasicObject
|
72
|
+
super
|
73
|
+
end
|
48
74
|
|
49
|
-
|
75
|
+
def add_option_in_place_of_name(klass, options)
|
76
|
+
if (keys = FILTER_NAME_OR_OPTION[klass.to_s]) && (keys && options.keys).empty?
|
77
|
+
options.merge(
|
78
|
+
"#{keys.first}": name.to_s.singularize.camelize.to_sym
|
79
|
+
)
|
80
|
+
else
|
81
|
+
options
|
50
82
|
end
|
51
83
|
end
|
52
84
|
|
53
|
-
|
85
|
+
# rubocop:disable Style/MissingRespondToMissing
|
86
|
+
def method_missing(*, &block)
|
87
|
+
super do |klass, names, options|
|
88
|
+
options = add_option_in_place_of_name(klass, options)
|
54
89
|
|
55
|
-
|
56
|
-
def classes
|
57
|
-
result = [Array]
|
90
|
+
filter = klass.new(names.first || '', options, &block)
|
58
91
|
|
59
|
-
|
60
|
-
ActiveRecord::Relation
|
61
|
-
ActiveRecord::Associations::CollectionProxy
|
62
|
-
].each do |name|
|
63
|
-
next unless (klass = name.safe_constantize)
|
64
|
-
result.push(klass)
|
65
|
-
end
|
92
|
+
filters[filters.size.to_s.to_sym] = filter
|
66
93
|
|
67
|
-
|
94
|
+
validate!(filter, names)
|
95
|
+
end
|
68
96
|
end
|
97
|
+
# rubocop:enable Style/MissingRespondToMissing
|
69
98
|
|
70
99
|
# @param filter [Filter]
|
71
100
|
# @param names [Array<Symbol>]
|
72
101
|
#
|
73
102
|
# @raise [InvalidFilterError]
|
74
103
|
def validate!(filter, names)
|
75
|
-
|
76
|
-
raise InvalidFilterError, 'multiple filters in array block'
|
77
|
-
end
|
104
|
+
raise InvalidFilterError, 'multiple filters in array block' if filters.size > 1
|
78
105
|
|
79
|
-
unless names.empty?
|
80
|
-
raise InvalidFilterError, 'attribute names in array block'
|
81
|
-
end
|
106
|
+
raise InvalidFilterError, 'attribute names in array block' unless names.empty?
|
82
107
|
|
83
|
-
if filter.default?
|
84
|
-
raise InvalidDefaultError, 'default values in array block'
|
85
|
-
end
|
108
|
+
raise InvalidDefaultError, 'default values in array block' if filter.default?
|
86
109
|
|
87
110
|
nil
|
88
111
|
end
|
@@ -1,13 +1,12 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module ActiveInteraction
|
5
|
-
class Base
|
4
|
+
class Base # rubocop:disable Lint/EmptyClass
|
6
5
|
# @!method self.boolean(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
|
-
# the attributes are Booleans. The strings `"1"` and `"
|
9
|
-
# (case-insensitive) are converted to `true` while the strings `"0"
|
10
|
-
# and `"
|
7
|
+
# the attributes are Booleans. The strings `"1"`, `"true"`, and `"on"`
|
8
|
+
# (case-insensitive) are converted to `true` while the strings `"0"`,
|
9
|
+
# `"false"`, and `"off"` are converted to `false`.
|
11
10
|
#
|
12
11
|
# @!macro filter_method_params
|
13
12
|
#
|
@@ -19,19 +18,34 @@ module ActiveInteraction
|
|
19
18
|
class BooleanFilter < Filter
|
20
19
|
register :boolean
|
21
20
|
|
22
|
-
def
|
21
|
+
def database_column_type
|
22
|
+
self.class.slug
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def matches?(value)
|
28
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
29
|
+
rescue NoMethodError # BasicObject
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def convert(value)
|
34
|
+
if value.respond_to?(:to_str)
|
35
|
+
value = value.to_str
|
36
|
+
value = nil if value.blank?
|
37
|
+
end
|
38
|
+
|
23
39
|
case value
|
24
|
-
when
|
40
|
+
when /\A(?:0|false|off)\z/i
|
25
41
|
false
|
26
|
-
when
|
42
|
+
when /\A(?:1|true|on)\z/i
|
27
43
|
true
|
28
44
|
else
|
29
45
|
super
|
30
46
|
end
|
31
|
-
|
32
|
-
|
33
|
-
def database_column_type
|
34
|
-
self.class.slug
|
47
|
+
rescue NoMethodError # BasicObject
|
48
|
+
super
|
35
49
|
end
|
36
50
|
end
|
37
51
|
end
|
@@ -1,8 +1,7 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module ActiveInteraction
|
5
|
-
class Base
|
4
|
+
class Base # rubocop:disable Lint/EmptyClass
|
6
5
|
# @!method self.date(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Dates. String values are processed using `parse`
|
@@ -1,8 +1,7 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module ActiveInteraction
|
5
|
-
class Base
|
4
|
+
class Base # rubocop:disable Lint/EmptyClass
|
6
5
|
# @!method self.date_time(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are DateTimes. String values are processed using
|
@@ -1,10 +1,9 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'bigdecimal'
|
5
4
|
|
6
5
|
module ActiveInteraction
|
7
|
-
class Base
|
6
|
+
class Base # rubocop:disable Lint/EmptyClass
|
8
7
|
# @!method self.decimal(*attributes, options = {})
|
9
8
|
# Creates accessors for the attributes and ensures that values passed to
|
10
9
|
# the attributes are BigDecimals. Numerics and String values are
|
@@ -14,46 +13,29 @@ module ActiveInteraction
|
|
14
13
|
#
|
15
14
|
# @example
|
16
15
|
# decimal :amount, digits: 4
|
17
|
-
#
|
18
|
-
# @since 1.2.0
|
19
16
|
end
|
20
17
|
|
21
18
|
# @private
|
22
19
|
class DecimalFilter < AbstractNumericFilter
|
23
20
|
register :decimal
|
24
21
|
|
25
|
-
def cast(value, _interaction)
|
26
|
-
case value
|
27
|
-
when Numeric
|
28
|
-
BigDecimal(value, digits)
|
29
|
-
when String
|
30
|
-
decimal_from_string(value)
|
31
|
-
else
|
32
|
-
super
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
22
|
private
|
37
23
|
|
38
|
-
# @return [Integer]
|
39
24
|
def digits
|
40
25
|
options.fetch(:digits, 0)
|
41
26
|
end
|
42
27
|
|
43
|
-
# @param value [String] string that has to be converted
|
44
|
-
#
|
45
|
-
# @return [BigDecimal]
|
46
|
-
#
|
47
|
-
# @raise [InvalidValueError] if given value can not be converted
|
48
|
-
def decimal_from_string(value)
|
49
|
-
Float(value)
|
50
|
-
BigDecimal(value, digits)
|
51
|
-
rescue ArgumentError
|
52
|
-
raise InvalidValueError, "Given value: #{value.inspect}"
|
53
|
-
end
|
54
|
-
|
55
28
|
def klass
|
56
29
|
BigDecimal
|
57
30
|
end
|
31
|
+
|
32
|
+
def converter(value)
|
33
|
+
# Ruby < 2.4 does not throw an error in BigDecimal
|
34
|
+
# for invalid strings. We'll simulate the error by
|
35
|
+
# calling Float.
|
36
|
+
Float(value) if value.is_a?(String)
|
37
|
+
|
38
|
+
BigDecimal(value, digits)
|
39
|
+
end
|
58
40
|
end
|
59
41
|
end
|