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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -1
  3. data/README.md +63 -28
  4. data/lib/active_interaction/array_input.rb +77 -0
  5. data/lib/active_interaction/base.rb +14 -98
  6. data/lib/active_interaction/concerns/active_recordable.rb +3 -3
  7. data/lib/active_interaction/concerns/missable.rb +2 -2
  8. data/lib/active_interaction/errors.rb +6 -88
  9. data/lib/active_interaction/exceptions.rb +47 -0
  10. data/lib/active_interaction/filter/column.rb +59 -0
  11. data/lib/active_interaction/filter/error.rb +40 -0
  12. data/lib/active_interaction/filter.rb +44 -53
  13. data/lib/active_interaction/filters/abstract_date_time_filter.rb +9 -6
  14. data/lib/active_interaction/filters/abstract_numeric_filter.rb +7 -3
  15. data/lib/active_interaction/filters/array_filter.rb +34 -6
  16. data/lib/active_interaction/filters/boolean_filter.rb +4 -3
  17. data/lib/active_interaction/filters/date_filter.rb +1 -1
  18. data/lib/active_interaction/filters/date_time_filter.rb +1 -1
  19. data/lib/active_interaction/filters/decimal_filter.rb +1 -1
  20. data/lib/active_interaction/filters/float_filter.rb +1 -1
  21. data/lib/active_interaction/filters/hash_filter.rb +23 -15
  22. data/lib/active_interaction/filters/integer_filter.rb +1 -1
  23. data/lib/active_interaction/filters/interface_filter.rb +12 -12
  24. data/lib/active_interaction/filters/object_filter.rb +9 -3
  25. data/lib/active_interaction/filters/record_filter.rb +21 -11
  26. data/lib/active_interaction/filters/string_filter.rb +1 -1
  27. data/lib/active_interaction/filters/symbol_filter.rb +1 -1
  28. data/lib/active_interaction/filters/time_filter.rb +4 -4
  29. data/lib/active_interaction/hash_input.rb +43 -0
  30. data/lib/active_interaction/input.rb +23 -0
  31. data/lib/active_interaction/inputs.rb +157 -46
  32. data/lib/active_interaction/locale/en.yml +0 -1
  33. data/lib/active_interaction/locale/fr.yml +0 -1
  34. data/lib/active_interaction/locale/it.yml +0 -1
  35. data/lib/active_interaction/locale/ja.yml +0 -1
  36. data/lib/active_interaction/locale/pt-BR.yml +0 -1
  37. data/lib/active_interaction/modules/validation.rb +6 -17
  38. data/lib/active_interaction/version.rb +1 -1
  39. data/lib/active_interaction.rb +43 -36
  40. data/spec/active_interaction/array_input_spec.rb +166 -0
  41. data/spec/active_interaction/base_spec.rb +15 -240
  42. data/spec/active_interaction/concerns/active_modelable_spec.rb +3 -3
  43. data/spec/active_interaction/concerns/active_recordable_spec.rb +7 -7
  44. data/spec/active_interaction/concerns/hashable_spec.rb +8 -8
  45. data/spec/active_interaction/concerns/missable_spec.rb +9 -9
  46. data/spec/active_interaction/concerns/runnable_spec.rb +34 -32
  47. data/spec/active_interaction/errors_spec.rb +60 -43
  48. data/spec/active_interaction/{filter_column_spec.rb → filter/column_spec.rb} +3 -10
  49. data/spec/active_interaction/filter_spec.rb +6 -6
  50. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +2 -2
  51. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +2 -2
  52. data/spec/active_interaction/filters/array_filter_spec.rb +99 -16
  53. data/spec/active_interaction/filters/boolean_filter_spec.rb +12 -11
  54. data/spec/active_interaction/filters/date_filter_spec.rb +32 -27
  55. data/spec/active_interaction/filters/date_time_filter_spec.rb +34 -29
  56. data/spec/active_interaction/filters/decimal_filter_spec.rb +20 -18
  57. data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
  58. data/spec/active_interaction/filters/float_filter_spec.rb +19 -17
  59. data/spec/active_interaction/filters/hash_filter_spec.rb +16 -18
  60. data/spec/active_interaction/filters/integer_filter_spec.rb +24 -22
  61. data/spec/active_interaction/filters/interface_filter_spec.rb +105 -82
  62. data/spec/active_interaction/filters/object_filter_spec.rb +52 -36
  63. data/spec/active_interaction/filters/record_filter_spec.rb +61 -39
  64. data/spec/active_interaction/filters/string_filter_spec.rb +7 -7
  65. data/spec/active_interaction/filters/symbol_filter_spec.rb +6 -6
  66. data/spec/active_interaction/filters/time_filter_spec.rb +57 -34
  67. data/spec/active_interaction/hash_input_spec.rb +58 -0
  68. data/spec/active_interaction/i18n_spec.rb +22 -17
  69. data/spec/active_interaction/inputs_spec.rb +167 -23
  70. data/spec/active_interaction/integration/array_interaction_spec.rb +3 -7
  71. data/spec/active_interaction/modules/validation_spec.rb +8 -31
  72. data/spec/spec_helper.rb +8 -0
  73. data/spec/support/concerns.rb +2 -2
  74. data/spec/support/filters.rb +27 -51
  75. data/spec/support/interactions.rb +4 -4
  76. metadata +40 -91
  77. data/lib/active_interaction/filter_column.rb +0 -57
@@ -15,19 +15,19 @@ BackupObjectThing = ObjectThing
15
15
 
16
16
  describe ActiveInteraction::ObjectFilter, :filter do
17
17
  include_context 'filters'
18
- it_behaves_like 'a filter'
19
-
20
18
  before do
21
19
  options[:class] = ObjectThing
22
20
  end
23
21
 
24
- describe '#cast' do
22
+ it_behaves_like 'a filter'
23
+
24
+ describe '#process' do
25
25
  let(:value) { ObjectThing.new }
26
- let(:result) { filter.send(:cast, value, nil) }
26
+ let(:result) { filter.process(value, nil) }
27
27
 
28
28
  context 'with an instance of the class' do
29
29
  it 'returns the instance' do
30
- expect(result).to eql value
30
+ expect(result.value).to eql value
31
31
  end
32
32
 
33
33
  context 'with an instance that is a subclass' do
@@ -35,18 +35,18 @@ describe ActiveInteraction::ObjectFilter, :filter do
35
35
  let(:value) { subclass.new }
36
36
 
37
37
  it 'returns the instance' do
38
- expect(result).to eql value
38
+ expect(result.value).to eql value
39
39
  end
40
40
  end
41
41
 
42
42
  it 'handles reconstantizing' do
43
- expect(result).to eql value
43
+ expect(result.value).to eql value
44
44
 
45
45
  Object.send(:remove_const, :ObjectThing)
46
46
  ObjectThing = BackupObjectThing # rubocop:disable Lint/ConstantDefinitionInBlock
47
47
  value = ObjectThing.new
48
48
 
49
- expect(filter.send(:cast, value, nil)).to eql value
49
+ expect(filter.process(value, nil).value).to eql value
50
50
  end
51
51
 
52
52
  it 'handles reconstantizing subclasses' do
@@ -57,11 +57,12 @@ describe ActiveInteraction::ObjectFilter, :filter do
57
57
  class SubObjectThing < ObjectThing; end # rubocop:disable Lint/ConstantDefinitionInBlock
58
58
  value = SubObjectThing.new
59
59
 
60
- expect(filter.send(:cast, value, nil)).to eql value
60
+ expect(filter.process(value, nil).value).to eql value
61
61
  end
62
62
 
63
63
  context 'without the class available' do
64
64
  before { Object.send(:remove_const, :ObjectThing) }
65
+
65
66
  after { ObjectThing = BackupObjectThing } # rubocop:disable Lint/ConstantDefinitionInBlock
66
67
 
67
68
  it 'does not raise an error on initialization' do
@@ -76,7 +77,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
76
77
  end
77
78
 
78
79
  it 'returns the instance' do
79
- expect(result).to eql value
80
+ expect(result.value).to eql value
80
81
  end
81
82
  end
82
83
 
@@ -86,7 +87,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
86
87
  before { options[:class] = ObjectThings }
87
88
 
88
89
  it 'returns the instance' do
89
- expect(result).to eql value
90
+ expect(result.value).to eql value
90
91
  end
91
92
  end
92
93
 
@@ -111,7 +112,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
111
112
  end
112
113
 
113
114
  it 'calls the class method' do
114
- expect(result).to eql ObjectThing.converter(value)
115
+ expect(result.value).to eql ObjectThing.converter(value)
115
116
  end
116
117
  end
117
118
 
@@ -121,7 +122,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
121
122
  end
122
123
 
123
124
  it 'gets called' do
124
- expect(result).to eql ObjectThing.converter(value)
125
+ expect(result.value).to eql ObjectThing.converter(value)
125
126
  end
126
127
  end
127
128
 
@@ -129,8 +130,13 @@ describe ActiveInteraction::ObjectFilter, :filter do
129
130
  let(:value) { ObjectThing.new }
130
131
 
131
132
  it 'does not call the converter' do
132
- expect(ObjectThing).to_not receive(:converter)
133
- expect(result).to eql value
133
+ allow(ObjectThing).to receive(:converter)
134
+ result.value
135
+ expect(ObjectThing).to_not have_received(:converter)
136
+ end
137
+
138
+ it 'returns the correct value' do
139
+ expect(result.value).to eql value
134
140
  end
135
141
  end
136
142
 
@@ -139,18 +145,29 @@ describe ActiveInteraction::ObjectFilter, :filter do
139
145
  let(:value) { subclass.new }
140
146
 
141
147
  it 'does not call the converter' do
142
- expect(subclass).to_not receive(:converter)
143
- expect(result).to eql value
148
+ allow(subclass).to receive(:converter)
149
+ result.value
150
+ expect(subclass).to_not have_received(:converter)
151
+ end
152
+
153
+ it 'returns the correct value' do
154
+ expect(result.value).to eql value
144
155
  end
145
156
  end
146
157
 
147
158
  context 'with a nil value' do
148
159
  let(:value) { nil }
160
+
149
161
  include_context 'optional'
150
162
 
151
163
  it 'returns nil' do
152
- expect(ObjectThing).to_not receive(:converter)
153
- expect(result).to eql value
164
+ allow(ObjectThing).to receive(:converter)
165
+ result.value
166
+ expect(ObjectThing).to_not have_received(:converter)
167
+ end
168
+
169
+ it 'returns the correct value' do
170
+ expect(result.value).to eql value
154
171
  end
155
172
  end
156
173
 
@@ -171,17 +188,14 @@ describe ActiveInteraction::ObjectFilter, :filter do
171
188
  options[:converter] = :converter_with_error
172
189
  end
173
190
 
174
- it 'raises an error' do
175
- expect do
176
- result
177
- end.to raise_error ActiveInteraction::InvalidValueError
191
+ it 'indicates an error' do
192
+ error = result.errors.first
193
+
194
+ expect(error).to be_an_instance_of ActiveInteraction::Filter::Error
195
+ expect(error.type).to be :invalid_type
178
196
  end
179
197
  end
180
- end
181
- end
182
198
 
183
- describe '#clean' do
184
- context 'with a converter' do
185
199
  context 'that returns a nil' do
186
200
  let(:value) { '' }
187
201
 
@@ -190,10 +204,11 @@ describe ActiveInteraction::ObjectFilter, :filter do
190
204
  options[:converter] = ->(_) {}
191
205
  end
192
206
 
193
- it 'raises an error' do
194
- expect do
195
- filter.clean(value, nil)
196
- end.to raise_error ActiveInteraction::InvalidValueError
207
+ it 'indicates an error' do
208
+ error = filter.process(value, nil).errors.first
209
+
210
+ expect(error).to be_an_instance_of ActiveInteraction::Filter::Error
211
+ expect(error.type).to be :invalid_type
197
212
  end
198
213
  end
199
214
 
@@ -204,10 +219,11 @@ describe ActiveInteraction::ObjectFilter, :filter do
204
219
  options[:converter] = ->(_) { 'invalid' }
205
220
  end
206
221
 
207
- it 'raises an error' do
208
- expect do
209
- filter.clean(value, nil)
210
- end.to raise_error ActiveInteraction::InvalidValueError
222
+ it 'indicates an error' do
223
+ error = filter.process(value, nil).errors.first
224
+
225
+ expect(error).to be_an_instance_of ActiveInteraction::Filter::Error
226
+ expect(error.type).to be :invalid_type
211
227
  end
212
228
  end
213
229
  end
@@ -215,7 +231,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
215
231
 
216
232
  describe '#database_column_type' do
217
233
  it 'returns :string' do
218
- expect(filter.database_column_type).to eql :string
234
+ expect(filter.database_column_type).to be :string
219
235
  end
220
236
  end
221
237
  end
@@ -14,11 +14,7 @@ class RecordThing
14
14
  end
15
15
 
16
16
  def self.finds_bad_value(_)
17
- ''
18
- end
19
-
20
- def self.passthrough(obj)
21
- obj
17
+ Object.new
22
18
  end
23
19
  end
24
20
 
@@ -27,23 +23,19 @@ BackupRecordThing = RecordThing
27
23
 
28
24
  describe ActiveInteraction::RecordFilter, :filter do
29
25
  include_context 'filters'
30
- it_behaves_like 'a filter'
31
-
32
26
  before do
33
27
  options[:class] = RecordThing
34
28
  end
35
29
 
36
- describe '#cast' do
37
- before do
38
- options[:finder] = :finder
39
- end
30
+ it_behaves_like 'a filter'
40
31
 
32
+ describe '#process' do
41
33
  let(:value) { RecordThing.new }
42
- let(:result) { filter.send(:cast, value, nil) }
34
+ let(:result) { filter.process(value, nil) }
43
35
 
44
36
  context 'with an instance of the class' do
45
37
  it 'returns the instance' do
46
- expect(result).to eql value
38
+ expect(result.value).to eql value
47
39
  end
48
40
 
49
41
  context 'with an instance that is a subclass' do
@@ -51,18 +43,18 @@ describe ActiveInteraction::RecordFilter, :filter do
51
43
  let(:value) { subclass.new }
52
44
 
53
45
  it 'returns the instance' do
54
- expect(result).to eql value
46
+ expect(result.value).to eql value
55
47
  end
56
48
  end
57
49
 
58
50
  it 'handles reconstantizing' do
59
- expect(result).to eql value
51
+ expect(result.value).to eql value
60
52
 
61
53
  Object.send(:remove_const, :RecordThing)
62
54
  RecordThing = BackupRecordThing # rubocop:disable Lint/ConstantDefinitionInBlock
63
55
  value = RecordThing.new
64
56
 
65
- expect(filter.send(:cast, value, nil)).to eql value
57
+ expect(filter.process(value, nil).value).to eql value
66
58
  end
67
59
 
68
60
  it 'handles reconstantizing subclasses' do
@@ -73,11 +65,12 @@ describe ActiveInteraction::RecordFilter, :filter do
73
65
  class SubRecordThing < RecordThing; end # rubocop:disable Lint/ConstantDefinitionInBlock
74
66
  value = SubRecordThing.new
75
67
 
76
- expect(filter.send(:cast, value, nil)).to eql value
68
+ expect(filter.process(value, nil).value).to eql value
77
69
  end
78
70
 
79
71
  context 'without the class available' do
80
72
  before { Object.send(:remove_const, :RecordThing) }
73
+
81
74
  after { RecordThing = BackupRecordThing } # rubocop:disable Lint/ConstantDefinitionInBlock
82
75
 
83
76
  it 'does not raise an error on initialization' do
@@ -92,7 +85,7 @@ describe ActiveInteraction::RecordFilter, :filter do
92
85
  end
93
86
 
94
87
  it 'returns the instance' do
95
- expect(result).to eql value
88
+ expect(result.value).to eql value
96
89
  end
97
90
  end
98
91
 
@@ -102,7 +95,7 @@ describe ActiveInteraction::RecordFilter, :filter do
102
95
  end
103
96
 
104
97
  it 'returns the instance' do
105
- expect(result).to eql value
98
+ expect(result.value).to eql value
106
99
  end
107
100
  end
108
101
 
@@ -112,7 +105,7 @@ describe ActiveInteraction::RecordFilter, :filter do
112
105
  before { options[:class] = RecordThings }
113
106
 
114
107
  it 'returns the instance' do
115
- expect(result).to eql value
108
+ expect(result.value).to eql value
116
109
  end
117
110
  end
118
111
 
@@ -129,48 +122,77 @@ describe ActiveInteraction::RecordFilter, :filter do
129
122
  end
130
123
 
131
124
  context 'with a value that does not match the class' do
132
- let(:value) { '' }
125
+ let(:value) { 1 }
133
126
 
134
- it 'calls the finder' do
135
- expect(result).to eql RecordThing.finder(value)
127
+ it 'calls the default finder' do
128
+ allow(RecordThing).to receive(:find)
129
+ result
130
+ expect(RecordThing).to have_received(:find).with(value)
136
131
  end
137
132
 
138
133
  context 'with a custom finder' do
134
+ before do
135
+ options[:finder] = :finder
136
+ end
137
+
139
138
  it 'calls the custom finder' do
140
- expect(result).to eql RecordThing.finder(value)
139
+ allow(RecordThing).to receive(:finder)
140
+ result
141
+ expect(RecordThing).to have_received(:finder).with(value)
141
142
  end
142
143
  end
143
- end
144
- end
145
144
 
146
- describe '#clean' do
147
- context 'with a value that does not match the class' do
148
145
  context 'that returns a nil' do
149
- let(:value) { '' }
146
+ let(:value) { 1 }
150
147
 
151
148
  before do
152
149
  options[:default] = RecordThing.new
153
150
  options[:finder] = :finds_nil
154
151
  end
155
152
 
156
- it 'raises an error' do
157
- expect do
158
- filter.clean(value, nil)
159
- end.to raise_error ActiveInteraction::InvalidValueError
153
+ it 'indicates an error' do
154
+ error = filter.process(value, nil).errors.first
155
+
156
+ expect(error).to be_an_instance_of ActiveInteraction::Filter::Error
157
+ expect(error.type).to be :invalid_type
160
158
  end
161
159
  end
162
160
 
163
161
  context 'that returns an invalid value' do
164
- let(:value) { '' }
162
+ let(:value) { 1 }
165
163
 
166
164
  before do
167
165
  options[:finder] = :finds_bad_value
168
166
  end
169
167
 
170
- it 'raises an error' do
171
- expect do
172
- filter.clean(value, nil)
173
- end.to raise_error ActiveInteraction::InvalidValueError
168
+ it 'indicates an error' do
169
+ error = filter.process(value, nil).errors.first
170
+
171
+ expect(error).to be_an_instance_of ActiveInteraction::Filter::Error
172
+ expect(error.type).to be :invalid_type
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'with a blank String' do
178
+ let(:value) { ' ' }
179
+
180
+ context 'optional' do
181
+ include_context 'optional'
182
+
183
+ it 'returns the default' do
184
+ expect(filter.process(value, nil).value).to eql options[:default]
185
+ end
186
+ end
187
+
188
+ context 'required' do
189
+ include_context 'required'
190
+
191
+ it 'indicates an error' do
192
+ error = filter.process(value, nil).errors.first
193
+
194
+ expect(error).to be_an_instance_of ActiveInteraction::Filter::Error
195
+ expect(error.type).to be :missing
174
196
  end
175
197
  end
176
198
  end
@@ -178,7 +200,7 @@ describe ActiveInteraction::RecordFilter, :filter do
178
200
 
179
201
  describe '#database_column_type' do
180
202
  it 'returns :string' do
181
- expect(filter.database_column_type).to eql :string
203
+ expect(filter.database_column_type).to be :string
182
204
  end
183
205
  end
184
206
  end
@@ -10,14 +10,14 @@ describe ActiveInteraction::StringFilter, :filter do
10
10
  end
11
11
  end
12
12
 
13
- describe '#cast' do
14
- let(:result) { filter.send(:cast, value, nil) }
13
+ describe '#process' do
14
+ let(:result) { filter.process(value, nil) }
15
15
 
16
16
  context 'with a String' do
17
17
  let(:value) { SecureRandom.hex }
18
18
 
19
19
  it 'returns the String' do
20
- expect(result).to eql value
20
+ expect(result.value).to eql value
21
21
  end
22
22
  end
23
23
 
@@ -31,7 +31,7 @@ describe ActiveInteraction::StringFilter, :filter do
31
31
  end
32
32
 
33
33
  it 'returns the String' do
34
- expect(result).to eql value.to_str
34
+ expect(result.value).to eql value.to_str
35
35
  end
36
36
  end
37
37
 
@@ -39,14 +39,14 @@ describe ActiveInteraction::StringFilter, :filter do
39
39
  let(:value) { " #{SecureRandom.hex} " }
40
40
 
41
41
  it 'returns the stripped string' do
42
- expect(result).to eql value.strip
42
+ expect(result.value).to eql value.strip
43
43
  end
44
44
 
45
45
  context 'without strip' do
46
46
  include_context 'without strip'
47
47
 
48
48
  it 'returns the String' do
49
- expect(result).to eql value
49
+ expect(result.value).to eql value
50
50
  end
51
51
  end
52
52
  end
@@ -54,7 +54,7 @@ describe ActiveInteraction::StringFilter, :filter do
54
54
 
55
55
  describe '#database_column_type' do
56
56
  it 'returns :string' do
57
- expect(filter.database_column_type).to eql :string
57
+ expect(filter.database_column_type).to be :string
58
58
  end
59
59
  end
60
60
  end
@@ -4,14 +4,14 @@ describe ActiveInteraction::SymbolFilter, :filter do
4
4
  include_context 'filters'
5
5
  it_behaves_like 'a filter'
6
6
 
7
- describe '#cast' do
8
- let(:result) { filter.send(:cast, value, nil) }
7
+ describe '#process' do
8
+ let(:result) { filter.process(value, nil) }
9
9
 
10
10
  context 'with a Symbol' do
11
11
  let(:value) { SecureRandom.hex.to_sym }
12
12
 
13
13
  it 'returns the Symbol' do
14
- expect(result).to eql value
14
+ expect(result.value).to eql value
15
15
  end
16
16
  end
17
17
 
@@ -25,7 +25,7 @@ describe ActiveInteraction::SymbolFilter, :filter do
25
25
  end
26
26
 
27
27
  it 'returns a symbol' do
28
- expect(result).to eql value.to_sym
28
+ expect(result.value).to eql value.to_sym
29
29
  end
30
30
  end
31
31
 
@@ -33,14 +33,14 @@ describe ActiveInteraction::SymbolFilter, :filter do
33
33
  let(:value) { SecureRandom.hex }
34
34
 
35
35
  it 'returns a Symbol' do
36
- expect(result).to eql value.to_sym
36
+ expect(result.value).to eql value.to_sym
37
37
  end
38
38
  end
39
39
  end
40
40
 
41
41
  describe '#database_column_type' do
42
42
  it 'returns :string' do
43
- expect(filter.database_column_type).to eql :string
43
+ expect(filter.database_column_type).to be :string
44
44
  end
45
45
  end
46
46
  end