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,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.symbol(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Symbols. Strings will be converted to Symbols.
|
@@ -17,15 +16,22 @@ module ActiveInteraction
|
|
17
16
|
class SymbolFilter < Filter
|
18
17
|
register :symbol
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
private
|
20
|
+
|
21
|
+
def matches?(value)
|
22
|
+
value.is_a?(Symbol)
|
23
|
+
rescue NoMethodError # BasicObject
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert(value)
|
28
|
+
if value.respond_to?(:to_sym)
|
25
29
|
value.to_sym
|
26
30
|
else
|
27
31
|
super
|
28
32
|
end
|
33
|
+
rescue NoMethodError # BasicObject
|
34
|
+
super
|
29
35
|
end
|
30
36
|
end
|
31
37
|
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.time(*attributes, options = {})
|
7
6
|
# Creates accessors for the attributes and ensures that values passed to
|
8
7
|
# the attributes are Times. Numeric values are processed using `at`.
|
@@ -24,34 +23,24 @@ module ActiveInteraction
|
|
24
23
|
class TimeFilter < AbstractDateTimeFilter
|
25
24
|
register :time
|
26
25
|
|
27
|
-
alias _klass klass
|
28
|
-
private :_klass
|
29
|
-
|
30
26
|
def initialize(name, options = {}, &block)
|
31
|
-
if options.key?(:format) &&
|
32
|
-
raise InvalidFilterError, 'format option unsupported with time zones'
|
33
|
-
end
|
27
|
+
raise InvalidFilterError, 'format option unsupported with time zones' if options.key?(:format) && time_with_zone?
|
34
28
|
|
35
29
|
super
|
36
30
|
end
|
37
31
|
|
38
|
-
def cast(value, _interaction)
|
39
|
-
case value
|
40
|
-
when Numeric
|
41
|
-
klass.at(value)
|
42
|
-
else
|
43
|
-
super
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
32
|
def database_column_type
|
48
33
|
:datetime
|
49
34
|
end
|
50
35
|
|
51
36
|
private
|
52
37
|
|
38
|
+
def time_with_zone?
|
39
|
+
Time.respond_to?(:zone) && !Time.zone.nil?
|
40
|
+
end
|
41
|
+
|
53
42
|
def klass
|
54
|
-
if
|
43
|
+
if time_with_zone?
|
55
44
|
Time.zone
|
56
45
|
else
|
57
46
|
super
|
@@ -59,7 +48,23 @@ module ActiveInteraction
|
|
59
48
|
end
|
60
49
|
|
61
50
|
def klasses
|
62
|
-
|
51
|
+
if time_with_zone?
|
52
|
+
[Time.zone.at(0).class, Time]
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def convert(value)
|
59
|
+
value = value.to_int if value.respond_to?(:to_int)
|
60
|
+
|
61
|
+
if value.is_a?(Numeric)
|
62
|
+
klass.at(value)
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
rescue NoMethodError # BasicObject
|
67
|
+
super
|
63
68
|
end
|
64
69
|
end
|
65
70
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'ostruct'
|
@@ -6,8 +5,6 @@ require 'ostruct'
|
|
6
5
|
module ActiveInteraction
|
7
6
|
# Holds a group of inputs together for passing from {Base} to {Filter}s.
|
8
7
|
#
|
9
|
-
# @since 1.2.0
|
10
|
-
#
|
11
8
|
# @private
|
12
9
|
class GroupedInput < OpenStruct
|
13
10
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveInteraction
|
4
|
+
# Holds inputs passed to the interaction.
|
5
|
+
class Inputs < DelegateClass(Hash)
|
6
|
+
class << self
|
7
|
+
# matches inputs like "key(1i)"
|
8
|
+
GROUPED_INPUT_PATTERN = /
|
9
|
+
\A
|
10
|
+
(?<key>.+) # extracts "key"
|
11
|
+
\((?<index>\d+)i\) # extracts "1"
|
12
|
+
\z
|
13
|
+
/x.freeze
|
14
|
+
private_constant :GROUPED_INPUT_PATTERN
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def keys_for_group?(keys, group_key)
|
18
|
+
search_key = /\A#{group_key}\(\d+i\)\z/
|
19
|
+
keys.any? { |key| search_key.match?(key) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Checking `syscall` is the result of what appears to be a bug in Ruby.
|
23
|
+
# https://bugs.ruby-lang.org/issues/15597
|
24
|
+
# @private
|
25
|
+
def reserved?(name)
|
26
|
+
name.to_s.start_with?('_interaction_') ||
|
27
|
+
name == :syscall ||
|
28
|
+
(
|
29
|
+
Base.method_defined?(name) &&
|
30
|
+
!Object.method_defined?(name)
|
31
|
+
) ||
|
32
|
+
(
|
33
|
+
Base.private_method_defined?(name) &&
|
34
|
+
!Object.private_method_defined?(name)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param inputs [Hash, ActionController::Parameters, ActiveInteraction::Inputs] Attribute values to set.
|
39
|
+
#
|
40
|
+
# @private
|
41
|
+
def process(inputs)
|
42
|
+
normalize_inputs!(inputs)
|
43
|
+
.stringify_keys
|
44
|
+
.sort
|
45
|
+
.each_with_object({}) do |(k, v), h|
|
46
|
+
next if reserved?(k)
|
47
|
+
|
48
|
+
if (group = GROUPED_INPUT_PATTERN.match(k))
|
49
|
+
assign_to_grouped_input!(h, group[:key], group[:index], v)
|
50
|
+
else
|
51
|
+
h[k.to_sym] = v
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def normalize_inputs!(inputs)
|
59
|
+
return inputs if inputs.is_a?(Hash) || inputs.is_a?(Inputs)
|
60
|
+
|
61
|
+
parameters = 'ActionController::Parameters'
|
62
|
+
klass = parameters.safe_constantize
|
63
|
+
return inputs.to_unsafe_h if klass && inputs.is_a?(klass)
|
64
|
+
|
65
|
+
raise ArgumentError, "inputs must be a hash or #{parameters}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def assign_to_grouped_input!(inputs, key, index, value)
|
69
|
+
key = key.to_sym
|
70
|
+
|
71
|
+
inputs[key] = GroupedInput.new unless inputs[key].is_a?(GroupedInput)
|
72
|
+
inputs[key][index] = value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@groups = {}
|
78
|
+
@groups.default_proc = ->(hash, key) { hash[key] = [] }
|
79
|
+
|
80
|
+
super(@inputs = {})
|
81
|
+
end
|
82
|
+
|
83
|
+
# Associates the `value` with the `key`. Allows the `key`/`value` pair to
|
84
|
+
# be associated with one or more groups.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# inputs.store(:key, :value)
|
88
|
+
# # => :value
|
89
|
+
# inputs.store(:key, :value, %i[a b])
|
90
|
+
# # => :value
|
91
|
+
#
|
92
|
+
# @param key [Object] The key to store the value under.
|
93
|
+
# @param value [Object] The value to store.
|
94
|
+
# @param groups [Array<Object>] The groups to store the pair under.
|
95
|
+
#
|
96
|
+
# @return [Object] value
|
97
|
+
# @private
|
98
|
+
def store(key, value, groups = [])
|
99
|
+
groups.each do |group|
|
100
|
+
@groups[group] << key
|
101
|
+
end
|
102
|
+
|
103
|
+
super(key, value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns inputs from the group name given.
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# inputs.group(:a)
|
110
|
+
# # => {key: :value}
|
111
|
+
#
|
112
|
+
# @param name [Object] Name of the group to return.
|
113
|
+
#
|
114
|
+
# @return [Hash] Inputs from the group name given.
|
115
|
+
# @private
|
116
|
+
def group(name)
|
117
|
+
@inputs.select { |k, _| @groups[name].include?(k) }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module ActiveInteraction
|
@@ -12,14 +11,13 @@ module ActiveInteraction
|
|
12
11
|
# @param inputs [Hash{Symbol => Object}]
|
13
12
|
def validate(context, filters, inputs)
|
14
13
|
filters.each_with_object([]) do |(name, filter), errors|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
14
|
+
filter.clean(inputs[name], context)
|
15
|
+
rescue NoDefaultError
|
16
|
+
nil
|
17
|
+
rescue InvalidNestedValueError,
|
18
|
+
InvalidValueError,
|
19
|
+
MissingValueError => e
|
20
|
+
errors << error_args(filter, e)
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
@@ -28,10 +26,9 @@ module ActiveInteraction
|
|
28
26
|
def error_args(filter, error)
|
29
27
|
case error
|
30
28
|
when InvalidNestedValueError
|
31
|
-
[filter.name, :invalid_nested,
|
32
|
-
name: error.filter_name.inspect, value: error.input_value.inspect]
|
29
|
+
[filter.name, :invalid_nested, { name: error.filter_name.inspect, value: error.input_value.inspect }]
|
33
30
|
when InvalidValueError
|
34
|
-
[filter.name, :invalid_type, type: type(filter)]
|
31
|
+
[filter.name, :invalid_type, { type: type(filter) }]
|
35
32
|
when MissingValueError
|
36
33
|
[filter.name, :missing]
|
37
34
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
1
|
require 'spec_helper'
|
4
2
|
require 'action_controller'
|
5
3
|
|
@@ -48,65 +46,6 @@ describe ActiveInteraction::Base do
|
|
48
46
|
expect(interaction.instance_variable_defined?(:"@#{key}")).to be false
|
49
47
|
end
|
50
48
|
|
51
|
-
context 'with invalid inputs' do
|
52
|
-
let(:inputs) { nil }
|
53
|
-
|
54
|
-
it 'raises an error' do
|
55
|
-
expect { interaction }.to raise_error ArgumentError
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
context 'with non-hash inputs' do
|
60
|
-
let(:inputs) { [[:k, :v]] }
|
61
|
-
|
62
|
-
it 'raises an error' do
|
63
|
-
expect { interaction }.to raise_error ArgumentError
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
context 'with ActionController::Parameters inputs' do
|
68
|
-
let(:inputs) { ActionController::Parameters.new }
|
69
|
-
|
70
|
-
it 'does not raise an error' do
|
71
|
-
expect { interaction }.to_not raise_error
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
context 'with a reader' do
|
76
|
-
let(:described_class) do
|
77
|
-
Class.new(TestInteraction) do
|
78
|
-
attr_reader :thing
|
79
|
-
|
80
|
-
validates :thing, presence: true
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
context 'validation' do
|
85
|
-
context 'failing' do
|
86
|
-
it 'returns an invalid outcome' do
|
87
|
-
expect(interaction).to be_invalid
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
context 'passing' do
|
92
|
-
before { inputs[:thing] = SecureRandom.hex }
|
93
|
-
|
94
|
-
it 'returns a valid outcome' do
|
95
|
-
expect(interaction).to be_valid
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
context 'with a single input' do
|
101
|
-
let(:thing) { SecureRandom.hex }
|
102
|
-
before { inputs[:thing] = thing }
|
103
|
-
|
104
|
-
it 'sets the attribute' do
|
105
|
-
expect(interaction.thing).to eql thing
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
49
|
context 'with a filter' do
|
111
50
|
let(:described_class) { InteractionWithFilter }
|
112
51
|
|
@@ -379,12 +318,10 @@ describe ActiveInteraction::Base do
|
|
379
318
|
end
|
380
319
|
|
381
320
|
it 'has the correct backtrace' do
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
.to include(InterruptInteraction.composition_location)
|
387
|
-
end
|
321
|
+
described_class.run!(inputs)
|
322
|
+
rescue ActiveInteraction::InvalidInteractionError => e
|
323
|
+
expect(e.backtrace)
|
324
|
+
.to include(InterruptInteraction.composition_location)
|
388
325
|
end
|
389
326
|
end
|
390
327
|
end
|
@@ -593,6 +530,32 @@ describe ActiveInteraction::Base do
|
|
593
530
|
expect(result).to be false
|
594
531
|
end
|
595
532
|
end
|
533
|
+
|
534
|
+
context 'multi-part date values' do
|
535
|
+
let(:described_class) do
|
536
|
+
Class.new(TestInteraction) do
|
537
|
+
date :thing,
|
538
|
+
default: nil
|
539
|
+
|
540
|
+
def execute
|
541
|
+
given?(:thing)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'returns true when the input is given' do
|
547
|
+
inputs.merge!(
|
548
|
+
'thing(1i)' => '2020',
|
549
|
+
'thing(2i)' => '12',
|
550
|
+
'thing(3i)' => '31'
|
551
|
+
)
|
552
|
+
expect(result).to be true
|
553
|
+
end
|
554
|
+
|
555
|
+
it 'returns false if not found' do
|
556
|
+
expect(result).to be false
|
557
|
+
end
|
558
|
+
end
|
596
559
|
end
|
597
560
|
|
598
561
|
context 'inheritance' do
|
@@ -643,32 +606,6 @@ describe ActiveInteraction::Base do
|
|
643
606
|
end
|
644
607
|
end
|
645
608
|
|
646
|
-
context 'predicates' do
|
647
|
-
let(:described_class) { InteractionWithFilter }
|
648
|
-
|
649
|
-
it 'responds to the predicate' do
|
650
|
-
expect(interaction.respond_to?(:thing?)).to be_truthy
|
651
|
-
end
|
652
|
-
|
653
|
-
context 'without a value' do
|
654
|
-
it 'returns false' do
|
655
|
-
expect(interaction.thing?).to be_falsey
|
656
|
-
end
|
657
|
-
end
|
658
|
-
|
659
|
-
context 'with a value' do
|
660
|
-
let(:thing) { rand }
|
661
|
-
|
662
|
-
before do
|
663
|
-
inputs[:thing] = thing
|
664
|
-
end
|
665
|
-
|
666
|
-
it 'returns true' do
|
667
|
-
expect(interaction.thing?).to be_truthy
|
668
|
-
end
|
669
|
-
end
|
670
|
-
end
|
671
|
-
|
672
609
|
describe '.import_filters' do
|
673
610
|
shared_context 'import_filters context' do |only, except|
|
674
611
|
let(:klass) { AddInteraction }
|
@@ -687,9 +624,11 @@ describe ActiveInteraction::Base do
|
|
687
624
|
include_context 'import_filters context', only, except
|
688
625
|
|
689
626
|
it 'imports the filters' do
|
690
|
-
expect(described_class.filters).to eql
|
691
|
-
.
|
692
|
-
|
627
|
+
expect(described_class.filters).to eql(
|
628
|
+
klass.filters
|
629
|
+
.select { |k, _| only.nil? ? true : [*only].include?(k) }
|
630
|
+
.reject { |k, _| except.nil? ? false : [*except].include?(k) }
|
631
|
+
)
|
693
632
|
end
|
694
633
|
|
695
634
|
it 'does not modify the source' do
|
@@ -698,11 +637,11 @@ describe ActiveInteraction::Base do
|
|
698
637
|
expect(klass.filters).to eql filters
|
699
638
|
end
|
700
639
|
|
701
|
-
it 'responds to readers
|
640
|
+
it 'responds to readers and writers' do
|
702
641
|
instance = described_class.new
|
703
642
|
|
704
|
-
described_class.filters.
|
705
|
-
[name, "#{name}="
|
643
|
+
described_class.filters.each_key do |name|
|
644
|
+
[name, "#{name}="].each do |method|
|
706
645
|
expect(instance).to respond_to method
|
707
646
|
end
|
708
647
|
end
|