active_interaction 3.8.3 → 4.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 +157 -0
- data/README.md +93 -107
- data/lib/active_interaction.rb +1 -7
- data/lib/active_interaction/base.rb +23 -26
- 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 +6 -12
- data/lib/active_interaction/errors.rb +3 -6
- data/lib/active_interaction/filter.rb +51 -37
- data/lib/active_interaction/filter_column.rb +0 -3
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +34 -36
- data/lib/active_interaction/filters/abstract_numeric_filter.rb +27 -17
- data/lib/active_interaction/filters/array_filter.rb +57 -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 +19 -22
- data/lib/active_interaction/grouped_input.rb +0 -3
- data/lib/active_interaction/inputs.rb +89 -0
- data/lib/active_interaction/modules/input_processor.rb +1 -4
- data/lib/active_interaction/modules/validation.rb +9 -12
- data/lib/active_interaction/version.rb +1 -3
- data/spec/active_interaction/base_spec.rb +13 -41
- 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 +1 -3
- data/spec/active_interaction/concerns/missable_spec.rb +0 -2
- data/spec/active_interaction/concerns/runnable_spec.rb +9 -13
- 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 +41 -15
- 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} +3 -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 +77 -52
- data/lib/active_interaction/backports.rb +0 -59
- data/lib/active_interaction/filters/abstract_filter.rb +0 -19
- data/spec/active_interaction/filters/abstract_filter_spec.rb +0 -8
@@ -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.float(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Floats. Integer and String values are converted
|
@@ -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.hash(*attributes, options = {}, &block)
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Hashes.
|
@@ -26,21 +25,46 @@ module ActiveInteraction
|
|
26
25
|
|
27
26
|
register :hash
|
28
27
|
|
29
|
-
|
30
|
-
case value
|
31
|
-
when Hash
|
32
|
-
value = value.with_indifferent_access
|
33
|
-
initial = strip? ? ActiveSupport::HashWithIndifferentAccess.new : value
|
28
|
+
private
|
34
29
|
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
def matches?(value)
|
31
|
+
value.is_a?(Hash)
|
32
|
+
rescue NoMethodError # BasicObject
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
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
|
+
def strip?
|
43
|
+
options.fetch(:strip, true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def adjust_output(value, context)
|
47
|
+
value = value.to_hash.with_indifferent_access
|
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
|
54
|
+
end
|
55
|
+
|
56
|
+
def convert(value)
|
57
|
+
if value.respond_to?(:to_hash)
|
58
|
+
value.to_hash
|
38
59
|
else
|
39
60
|
super
|
40
61
|
end
|
62
|
+
rescue NoMethodError # BasicObject
|
63
|
+
super
|
41
64
|
end
|
42
65
|
|
43
|
-
|
66
|
+
# rubocop:disable Style/MissingRespondToMissing
|
67
|
+
def method_missing(*args, &block)
|
44
68
|
super(*args) do |klass, names, options|
|
45
69
|
raise InvalidFilterError, 'missing attribute name' if names.empty?
|
46
70
|
|
@@ -49,28 +73,14 @@ module ActiveInteraction
|
|
49
73
|
end
|
50
74
|
end
|
51
75
|
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def clean_value(h, name, filter, value, context)
|
56
|
-
h[name] = filter.clean(value[name], context)
|
57
|
-
rescue InvalidValueError, MissingValueError
|
58
|
-
raise InvalidNestedValueError.new(name, value[name])
|
59
|
-
end
|
76
|
+
# rubocop:enable Style/MissingRespondToMissing
|
60
77
|
|
61
78
|
def raw_default(*)
|
62
79
|
value = super
|
63
80
|
|
64
|
-
if value.is_a?(Hash) && !value.empty?
|
65
|
-
raise InvalidDefaultError, "#{name}: #{value.inspect}"
|
66
|
-
end
|
81
|
+
raise InvalidDefaultError, "#{name}: #{value.inspect}" if value.is_a?(Hash) && !value.empty?
|
67
82
|
|
68
83
|
value
|
69
84
|
end
|
70
|
-
|
71
|
-
# @return [Boolean]
|
72
|
-
def strip?
|
73
|
-
options.fetch(:strip, true)
|
74
|
-
end
|
75
85
|
end
|
76
86
|
end
|
@@ -1,14 +1,16 @@
|
|
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.integer(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Integers. String values are converted into
|
9
8
|
# Integers.
|
10
9
|
#
|
11
10
|
# @!macro filter_method_params
|
11
|
+
# @option options [Integer] :base (10) The base used to convert strings
|
12
|
+
# into integers. When set to `0` it will honor radix indicators (i.e.
|
13
|
+
# 0, 0b, and 0x).
|
12
14
|
#
|
13
15
|
# @example
|
14
16
|
# integer :quantity
|
@@ -20,15 +22,12 @@ module ActiveInteraction
|
|
20
22
|
|
21
23
|
private
|
22
24
|
|
23
|
-
# @return [Integer]
|
24
25
|
def base
|
25
|
-
options.fetch(:base,
|
26
|
+
options.fetch(:base, 10)
|
26
27
|
end
|
27
28
|
|
28
|
-
def
|
29
|
-
Integer(value, base)
|
30
|
-
rescue ArgumentError
|
31
|
-
_cast(value, context)
|
29
|
+
def converter(value)
|
30
|
+
Integer(value, value.is_a?(String) ? base : 0)
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
@@ -1,18 +1,24 @@
|
|
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.interface(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
|
-
# the attributes implement an interface.
|
7
|
+
# the attributes implement an interface. An interface can be based on a
|
8
|
+
# set of methods or the existance of a class or module in the ancestors
|
9
|
+
# of the passed value.
|
9
10
|
#
|
10
11
|
# @!macro filter_method_params
|
11
|
-
# @option options [
|
12
|
+
# @option options [Constant, String, Symbol] :from (use the attribute
|
13
|
+
# name) The class or module representing the interface to check for.
|
14
|
+
# @option options [Array<String, Symbol>] :methods ([]) the methods that
|
12
15
|
# objects conforming to this interface should respond to
|
13
16
|
#
|
14
17
|
# @example
|
15
|
-
# interface :
|
18
|
+
# interface :concern
|
19
|
+
# @example
|
20
|
+
# interface :person,
|
21
|
+
# from: Manageable
|
16
22
|
# @example
|
17
23
|
# interface :serializer,
|
18
24
|
# methods: %i[dump load]
|
@@ -22,24 +28,52 @@ module ActiveInteraction
|
|
22
28
|
class InterfaceFilter < Filter
|
23
29
|
register :interface
|
24
30
|
|
25
|
-
def
|
26
|
-
|
31
|
+
def initialize(name, options = {}, &block)
|
32
|
+
if options.key?(:methods) && options.key?(:from)
|
33
|
+
raise InvalidFilterError,
|
34
|
+
'method and from options cannot both be passed'
|
35
|
+
end
|
36
|
+
|
37
|
+
super
|
27
38
|
end
|
28
39
|
|
29
40
|
private
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
|
42
|
+
def from
|
43
|
+
const_name = options.fetch(:from, name).to_s.camelize
|
44
|
+
Object.const_get(const_name)
|
45
|
+
rescue NameError
|
46
|
+
raise InvalidNameError,
|
47
|
+
"constant #{const_name.inspect} does not exist"
|
48
|
+
end
|
49
|
+
|
34
50
|
def matches?(object)
|
35
|
-
|
51
|
+
return matches_methods?(object) if options.key?(:methods)
|
52
|
+
|
53
|
+
const = from
|
54
|
+
if checking_class_inheritance?(object, const)
|
55
|
+
class_inherits_from?(object, const)
|
56
|
+
else
|
57
|
+
singleton_ancestor?(object, const)
|
58
|
+
end
|
36
59
|
rescue NoMethodError
|
37
60
|
false
|
38
61
|
end
|
39
62
|
|
40
|
-
|
41
|
-
|
42
|
-
|
63
|
+
def matches_methods?(object)
|
64
|
+
options.fetch(:methods, []).all? { |method| object.respond_to?(method) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def checking_class_inheritance?(object, from)
|
68
|
+
object.is_a?(Class) && from.is_a?(Class)
|
69
|
+
end
|
70
|
+
|
71
|
+
def class_inherits_from?(klass, inherits_from)
|
72
|
+
klass != inherits_from && klass.ancestors.include?(inherits_from)
|
73
|
+
end
|
74
|
+
|
75
|
+
def singleton_ancestor?(object, from)
|
76
|
+
object.class != from && object.singleton_class.ancestors.include?(from)
|
43
77
|
end
|
44
78
|
end
|
45
79
|
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.object(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are the correct class.
|
@@ -29,69 +28,43 @@ module ActiveInteraction
|
|
29
28
|
class ObjectFilter < Filter
|
30
29
|
register :object
|
31
30
|
|
32
|
-
# rubocop:disable Metrics/MethodLength
|
33
|
-
def cast(value, context, reconstantize: true, convert: true)
|
34
|
-
@klass ||= klass
|
35
|
-
|
36
|
-
if matches?(value)
|
37
|
-
value
|
38
|
-
elsif reconstantize
|
39
|
-
@klass = klass
|
40
|
-
public_send(__method__, value, context,
|
41
|
-
reconstantize: false,
|
42
|
-
convert: convert
|
43
|
-
)
|
44
|
-
elsif !value.nil? && convert && (converter = options[:converter])
|
45
|
-
value = convert(klass, value, converter)
|
46
|
-
public_send(__method__, value, context,
|
47
|
-
reconstantize: reconstantize,
|
48
|
-
convert: false
|
49
|
-
)
|
50
|
-
else
|
51
|
-
super(value, context)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
# rubocop:enable Metrics/MethodLength
|
55
|
-
|
56
31
|
private
|
57
32
|
|
58
|
-
# @return [Class]
|
59
|
-
#
|
60
|
-
# @raise [InvalidClassError]
|
61
33
|
def klass
|
62
34
|
klass_name = options.fetch(:class, name).to_s.camelize
|
63
35
|
Object.const_get(klass_name)
|
64
36
|
rescue NameError
|
65
|
-
raise
|
37
|
+
raise InvalidNameError, "class #{klass_name.inspect} does not exist"
|
66
38
|
end
|
67
39
|
|
68
|
-
# @param value [Object]
|
69
|
-
#
|
70
|
-
# @return [Boolean]
|
71
40
|
def matches?(value)
|
72
|
-
|
73
|
-
|
41
|
+
value.class <= klass
|
42
|
+
rescue NoMethodError
|
43
|
+
false
|
74
44
|
end
|
75
45
|
|
76
|
-
def convert(
|
77
|
-
result
|
78
|
-
|
79
|
-
|
80
|
-
converter.call(value)
|
81
|
-
when Symbol
|
82
|
-
klass.public_send(converter, value)
|
83
|
-
else
|
84
|
-
raise InvalidConverterError,
|
85
|
-
"#{converter.inspect} is not a valid converter"
|
86
|
-
end
|
87
|
-
|
88
|
-
raise InvalidValueError if result.nil?
|
89
|
-
|
90
|
-
result
|
46
|
+
def convert(value)
|
47
|
+
converter(value).tap do |result|
|
48
|
+
raise InvalidValueError if result.nil?
|
49
|
+
end
|
91
50
|
rescue StandardError => e
|
92
51
|
raise e if e.is_a?(InvalidConverterError)
|
93
52
|
|
94
53
|
raise InvalidValueError
|
95
54
|
end
|
55
|
+
|
56
|
+
def converter(value)
|
57
|
+
return value unless (converter = options[:converter])
|
58
|
+
|
59
|
+
case converter
|
60
|
+
when Proc
|
61
|
+
converter.call(value)
|
62
|
+
when Symbol
|
63
|
+
klass.public_send(converter, value)
|
64
|
+
else
|
65
|
+
raise InvalidConverterError,
|
66
|
+
"#{converter.inspect} is not a valid converter"
|
67
|
+
end
|
68
|
+
end
|
96
69
|
end
|
97
70
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveInteraction
|
4
|
-
class Base
|
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
7
|
# the attributes are the correct class.
|
@@ -28,49 +28,24 @@ module ActiveInteraction
|
|
28
28
|
class RecordFilter < Filter
|
29
29
|
register :record
|
30
30
|
|
31
|
-
# rubocop:disable Metrics/MethodLength
|
32
|
-
def cast(value, context, reconstantize: true, convert: true)
|
33
|
-
@klass ||= klass
|
34
|
-
|
35
|
-
if matches?(value)
|
36
|
-
value
|
37
|
-
elsif reconstantize
|
38
|
-
@klass = klass
|
39
|
-
public_send(__method__, value, context,
|
40
|
-
reconstantize: false,
|
41
|
-
convert: convert
|
42
|
-
)
|
43
|
-
elsif !value.nil? && convert
|
44
|
-
finder = options.fetch(:finder, :find)
|
45
|
-
value = find(klass, value, finder)
|
46
|
-
public_send(__method__, value, context,
|
47
|
-
reconstantize: reconstantize,
|
48
|
-
convert: false
|
49
|
-
)
|
50
|
-
else
|
51
|
-
super(value, context)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
# rubocop:enable Metrics/MethodLength
|
55
|
-
|
56
31
|
private
|
57
32
|
|
58
|
-
# @return [Class]
|
59
|
-
#
|
60
|
-
# @raise [InvalidClassError]
|
61
33
|
def klass
|
62
34
|
klass_name = options.fetch(:class, name).to_s.camelize
|
63
35
|
Object.const_get(klass_name)
|
64
36
|
rescue NameError
|
65
|
-
raise
|
37
|
+
raise InvalidNameError, "class #{klass_name.inspect} does not exist"
|
66
38
|
end
|
67
39
|
|
68
|
-
# @param value [Object]
|
69
|
-
#
|
70
|
-
# @return [Boolean]
|
71
40
|
def matches?(value)
|
72
|
-
|
73
|
-
|
41
|
+
value.class <= klass
|
42
|
+
rescue NoMethodError
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def convert(value)
|
47
|
+
finder = options.fetch(:finder, :find)
|
48
|
+
find(klass, value, finder)
|
74
49
|
end
|
75
50
|
|
76
51
|
def find(klass, value, finder)
|
@@ -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.string(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Strings.
|
@@ -21,20 +20,30 @@ module ActiveInteraction
|
|
21
20
|
class StringFilter < Filter
|
22
21
|
register :string
|
23
22
|
|
24
|
-
def cast(value, _interaction)
|
25
|
-
case value
|
26
|
-
when String
|
27
|
-
strip? ? value.strip : value
|
28
|
-
else
|
29
|
-
super
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
23
|
private
|
34
24
|
|
35
|
-
# @return [Boolean]
|
36
25
|
def strip?
|
37
26
|
options.fetch(:strip, true)
|
38
27
|
end
|
28
|
+
|
29
|
+
def matches?(value)
|
30
|
+
value.is_a?(String)
|
31
|
+
rescue NoMethodError # BasicObject
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def adjust_output(value, _context)
|
36
|
+
strip? ? value.strip : value
|
37
|
+
end
|
38
|
+
|
39
|
+
def convert(value)
|
40
|
+
if value.respond_to?(:to_str)
|
41
|
+
value.to_str
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
rescue NoMethodError # BasicObject
|
46
|
+
super
|
47
|
+
end
|
39
48
|
end
|
40
49
|
end
|