active_interaction 5.1.0 → 5.5.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/README.md +11 -12
  4. data/lib/active_interaction/base.rb +2 -0
  5. data/lib/active_interaction/errors.rb +16 -16
  6. data/lib/active_interaction/filter.rb +9 -12
  7. data/lib/active_interaction/filters/array_filter.rb +1 -1
  8. data/lib/active_interaction/filters/hash_filter.rb +5 -1
  9. data/lib/active_interaction/grouped_input.rb +23 -3
  10. data/lib/active_interaction/locale/es.yml +23 -0
  11. data/lib/active_interaction/version.rb +1 -1
  12. metadata +13 -115
  13. data/spec/active_interaction/array_input_spec.rb +0 -166
  14. data/spec/active_interaction/base_spec.rb +0 -537
  15. data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -45
  16. data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -49
  17. data/spec/active_interaction/concerns/hashable_spec.rb +0 -46
  18. data/spec/active_interaction/concerns/missable_spec.rb +0 -99
  19. data/spec/active_interaction/concerns/runnable_spec.rb +0 -397
  20. data/spec/active_interaction/errors_spec.rb +0 -196
  21. data/spec/active_interaction/filter/column_spec.rb +0 -87
  22. data/spec/active_interaction/filter_spec.rb +0 -39
  23. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +0 -13
  24. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +0 -13
  25. data/spec/active_interaction/filters/array_filter_spec.rb +0 -245
  26. data/spec/active_interaction/filters/boolean_filter_spec.rb +0 -87
  27. data/spec/active_interaction/filters/date_filter_spec.rb +0 -178
  28. data/spec/active_interaction/filters/date_time_filter_spec.rb +0 -189
  29. data/spec/active_interaction/filters/decimal_filter_spec.rb +0 -126
  30. data/spec/active_interaction/filters/file_filter_spec.rb +0 -40
  31. data/spec/active_interaction/filters/float_filter_spec.rb +0 -110
  32. data/spec/active_interaction/filters/hash_filter_spec.rb +0 -129
  33. data/spec/active_interaction/filters/integer_filter_spec.rb +0 -104
  34. data/spec/active_interaction/filters/interface_filter_spec.rb +0 -461
  35. data/spec/active_interaction/filters/object_filter_spec.rb +0 -237
  36. data/spec/active_interaction/filters/record_filter_spec.rb +0 -206
  37. data/spec/active_interaction/filters/string_filter_spec.rb +0 -60
  38. data/spec/active_interaction/filters/symbol_filter_spec.rb +0 -46
  39. data/spec/active_interaction/filters/time_filter_spec.rb +0 -251
  40. data/spec/active_interaction/grouped_input_spec.rb +0 -17
  41. data/spec/active_interaction/hash_input_spec.rb +0 -58
  42. data/spec/active_interaction/i18n_spec.rb +0 -113
  43. data/spec/active_interaction/inputs_spec.rb +0 -266
  44. data/spec/active_interaction/integration/array_interaction_spec.rb +0 -88
  45. data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -5
  46. data/spec/active_interaction/integration/date_interaction_spec.rb +0 -5
  47. data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -5
  48. data/spec/active_interaction/integration/file_interaction_spec.rb +0 -18
  49. data/spec/active_interaction/integration/float_interaction_spec.rb +0 -5
  50. data/spec/active_interaction/integration/hash_interaction_spec.rb +0 -76
  51. data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -5
  52. data/spec/active_interaction/integration/interface_interaction_spec.rb +0 -19
  53. data/spec/active_interaction/integration/object_interaction_spec.rb +0 -14
  54. data/spec/active_interaction/integration/record_integration_spec.rb +0 -5
  55. data/spec/active_interaction/integration/string_interaction_spec.rb +0 -5
  56. data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -5
  57. data/spec/active_interaction/integration/time_interaction_spec.rb +0 -88
  58. data/spec/active_interaction/modules/validation_spec.rb +0 -57
  59. data/spec/spec_helper.rb +0 -20
  60. data/spec/support/concerns.rb +0 -13
  61. data/spec/support/filters.rb +0 -227
  62. data/spec/support/interactions.rb +0 -124
@@ -1,45 +0,0 @@
1
- require 'spec_helper'
2
-
3
- shared_examples_for 'ActiveModel' do
4
- it 'includes ActiveModel::Conversion' do
5
- expect(subject).to be_a_kind_of ActiveModel::Conversion
6
- end
7
-
8
- it 'includes ActiveModel::Validations' do
9
- expect(subject).to be_a_kind_of ActiveModel::Validations
10
- end
11
-
12
- it 'extends ActiveModel::Naming' do
13
- expect(subject.class).to be_a_kind_of ActiveModel::Naming
14
- end
15
- end
16
-
17
- describe ActiveInteraction::ActiveModelable do
18
- include_context 'concerns', described_class
19
-
20
- it_behaves_like 'ActiveModel'
21
-
22
- describe '.i18n_scope' do
23
- it 'returns the scope' do
24
- expect(klass.i18n_scope).to be :active_interaction
25
- end
26
- end
27
-
28
- describe '#i18n_scope' do
29
- it 'returns the scope' do
30
- expect(instance.i18n_scope).to be :active_interaction
31
- end
32
- end
33
-
34
- describe '#new_record?' do
35
- it 'returns true' do
36
- expect(instance).to be_new_record
37
- end
38
- end
39
-
40
- describe '#persisted?' do
41
- it 'returns false' do
42
- expect(instance).to_not be_persisted
43
- end
44
- end
45
- end
@@ -1,49 +0,0 @@
1
- require 'spec_helper'
2
-
3
- InteractionWithFloatFilter = Class.new(TestInteraction) do
4
- float :thing
5
- end
6
-
7
- describe ActiveInteraction::ActiveRecordable do
8
- include_context 'interactions'
9
-
10
- let(:described_class) { InteractionWithFloatFilter }
11
-
12
- describe '#column_for_attribute(name)' do
13
- let(:column) { outcome.column_for_attribute(name) }
14
-
15
- context 'name is not an input name' do
16
- let(:name) { SecureRandom.hex }
17
-
18
- it 'returns nil if the attribute cannot be found' do
19
- expect(column).to be_nil
20
- end
21
- end
22
-
23
- context 'name is an input name' do
24
- let(:name) { described_class.filters.keys.first }
25
-
26
- it 'returns a Filter::Column' do
27
- expect(column).to be_a ActiveInteraction::Filter::Column
28
- end
29
-
30
- it 'returns a Filter::Column of type boolean' do
31
- expect(column.type).to be :float
32
- end
33
- end
34
- end
35
-
36
- describe '#has_attribute?' do
37
- it 'returns true if the filter exists' do
38
- expect(outcome).to have_attribute(:thing)
39
- end
40
-
41
- it 'works with strings' do
42
- expect(outcome).to have_attribute('thing')
43
- end
44
-
45
- it 'returns false if the filter does not exist' do
46
- expect(outcome).to_not have_attribute(:not_a_filter)
47
- end
48
- end
49
- end
@@ -1,46 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActiveInteraction::Hashable do
4
- include_context 'concerns', described_class
5
-
6
- describe '#hash(*args, &block)' do
7
- context 'with no arguments' do
8
- let(:hash) { instance.hash }
9
-
10
- it 'returns an Integer' do
11
- expect(hash).to be_an Integer
12
- end
13
- end
14
-
15
- context 'with arguments' do
16
- let(:arguments) { [:attribute, {}] }
17
- let(:hash) { instance.hash(*arguments) }
18
-
19
- before { allow(instance).to receive(:method_missing) }
20
-
21
- it 'calls method_missing' do
22
- hash
23
- expect(instance).to have_received(:method_missing).once
24
- .with(:hash, *arguments)
25
- end
26
-
27
- context 'with a block' do
28
- let(:block) { proc {} }
29
- let(:hash) { instance.hash(*arguments, &block) }
30
-
31
- it 'calls method_missing' do
32
- hash
33
- expect(instance).to have_received(:method_missing).once
34
- .with(:hash, *arguments)
35
- end
36
-
37
- it 'passes the block to method_missing' do
38
- allow(instance).to receive(:method_missing) do |*, &other_block|
39
- expect(other_block).to equal block
40
- end
41
- hash(&block)
42
- end
43
- end
44
- end
45
- end
46
- end
@@ -1,99 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActiveInteraction::Missable do
4
- include_context 'concerns', described_class
5
-
6
- describe '#respond_to?(slug, include_all = false)' do
7
- context 'with invalid slug' do
8
- let(:slug) { :slug }
9
-
10
- it 'returns false' do
11
- expect(instance).to_not respond_to(slug)
12
- end
13
- end
14
-
15
- context 'with valid slug' do
16
- let(:slug) { :boolean }
17
-
18
- it 'returns true' do
19
- expect(instance).to respond_to(slug)
20
- end
21
- end
22
- end
23
-
24
- describe '#method(sym)' do
25
- context 'with invalid slug' do
26
- let(:slug) { :slug }
27
-
28
- it 'returns false' do
29
- expect { instance.method(slug) }.to raise_error NameError
30
- end
31
- end
32
-
33
- context 'with valid slug' do
34
- let(:slug) { :boolean }
35
-
36
- it 'returns true' do
37
- expect(instance.method(slug)).to be_a Method
38
- end
39
- end
40
- end
41
-
42
- describe '#method_missing' do
43
- context 'with invalid slug' do
44
- let(:slug) { :slug }
45
-
46
- it 'calls super' do
47
- expect do
48
- instance.public_send(slug)
49
- end.to raise_error NameError
50
- end
51
- end
52
-
53
- context 'with valid slug' do
54
- let(:filter) { ActiveInteraction::Filter.factory(slug) }
55
- let(:slug) { :boolean }
56
-
57
- it 'returns self' do
58
- expect(instance.public_send(slug)).to eql instance
59
- end
60
-
61
- it 'yields' do
62
- expect do |b|
63
- instance.public_send(slug, &b)
64
- end.to yield_with_args(filter, [], {})
65
- end
66
-
67
- context 'with names' do
68
- let(:names) { %i[a b c] }
69
-
70
- it 'yields' do
71
- expect do |b|
72
- instance.public_send(:boolean, *names, &b)
73
- end.to yield_with_args(filter, names, {})
74
- end
75
- end
76
-
77
- context 'with options' do
78
- let(:options) { { a: nil, b: false, c: true } }
79
-
80
- it 'yields' do
81
- expect do |b|
82
- instance.public_send(:boolean, options, &b)
83
- end.to yield_with_args(filter, [], options)
84
- end
85
- end
86
-
87
- context 'with names & options' do
88
- let(:names) { %i[a b c] }
89
- let(:options) { { a: nil, b: false, c: true } }
90
-
91
- it 'yields' do
92
- expect do |b|
93
- instance.public_send(:boolean, *names, options, &b)
94
- end.to yield_with_args(filter, names, options)
95
- end
96
- end
97
- end
98
- end
99
- end
@@ -1,397 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActiveInteraction::Runnable do
4
- include_context 'concerns', described_class
5
-
6
- class WrappableFailingInteraction # rubocop:disable Lint/ConstantDefinitionInBlock
7
- include ActiveInteraction::Runnable
8
-
9
- def execute
10
- errors.add(:base)
11
- end
12
- end
13
-
14
- shared_context 'with an error' do
15
- before { instance.errors.add(:base) }
16
- end
17
-
18
- shared_context 'with a validator' do
19
- before { klass.validate { errors.add(:base) } }
20
- end
21
-
22
- shared_context 'with #execute defined' do
23
- before { klass.send(:define_method, :execute) { rand } }
24
- end
25
-
26
- context 'validations' do
27
- describe '#runtime_errors' do
28
- include_context 'with an error'
29
-
30
- it 'is invalid' do
31
- instance.result = nil
32
- expect(instance).to_not be_valid
33
- end
34
-
35
- it 'becomes valid if errors are cleared' do
36
- instance.result = nil
37
- instance.errors.clear
38
- instance.result = nil
39
- expect(instance).to be_valid
40
- end
41
- end
42
- end
43
-
44
- context 'callbacks' do
45
- describe '.set_callback' do
46
- include_context 'with #execute defined'
47
-
48
- shared_examples 'set_callback examples' do |name|
49
- context name do
50
- it 'does not raise an error' do
51
- expect do
52
- klass.set_callback name, :before, -> {}
53
- end.to_not raise_error
54
- end
55
-
56
- %i[after around before].each do |type|
57
- it type do
58
- has_run = false
59
-
60
- klass.set_callback name, type, -> { has_run = true }
61
-
62
- klass.run
63
- expect(has_run).to be_truthy
64
- end
65
- end
66
- end
67
- end
68
-
69
- include_examples 'set_callback examples', :validate
70
- include_examples 'set_callback examples', :execute
71
-
72
- context 'execute with composed interaction' do
73
- class WithFailingCompose # rubocop:disable Lint/ConstantDefinitionInBlock
74
- include ActiveInteraction::Runnable
75
-
76
- def execute
77
- compose(WrappableFailingInteraction)
78
- end
79
- end
80
-
81
- context 'around' do
82
- it 'is yielded errors from composed interactions' do
83
- block_result = nil
84
- WithFailingCompose.set_callback :execute, :around do |_, block|
85
- block_result = block.call
86
- end
87
-
88
- WithFailingCompose.run
89
- expect(block_result).to be_an(ActiveInteraction::Errors)
90
- expect(block_result).to include(:base)
91
- end
92
- end
93
-
94
- context 'after' do
95
- it 'is yielded errors from composed interactions' do
96
- has_run = false
97
- WithFailingCompose.set_callback :execute, :after do
98
- has_run = true
99
- end
100
-
101
- WithFailingCompose.run
102
- expect(has_run).to be_truthy
103
- end
104
-
105
- context 'using if' do
106
- it 'yields errors to the if' do
107
- has_run = false
108
- WithFailingCompose.set_callback :execute, :after, if: -> { errors.any? } do
109
- has_run = true
110
- end
111
-
112
- WithFailingCompose.run
113
- expect(has_run).to be_truthy
114
- end
115
- end
116
- end
117
- end
118
- end
119
- end
120
-
121
- describe '#errors' do
122
- it 'returns the errors' do
123
- expect(instance.errors).to be_an ActiveInteraction::Errors
124
- end
125
- end
126
-
127
- describe '#execute' do
128
- it 'raises an error' do
129
- expect { instance.execute }.to raise_error NotImplementedError
130
- end
131
- end
132
-
133
- describe '#result' do
134
- it 'returns the result' do
135
- expect(instance.result).to be_nil
136
- end
137
- end
138
-
139
- describe '#result=' do
140
- let(:result) { double }
141
-
142
- it 'returns the result' do
143
- expect((instance.result = result)).to eql result
144
- end
145
-
146
- it 'sets the result' do
147
- instance.result = result
148
- expect(instance.result).to eql result
149
- end
150
-
151
- context 'with an error' do
152
- include_context 'with an error'
153
-
154
- it 'sets the result' do
155
- instance.result = result
156
- expect(instance.result).to eql result
157
- end
158
- end
159
-
160
- context 'with a validator' do
161
- include_context 'with a validator'
162
-
163
- it 'sets the result' do
164
- instance.result = result
165
- expect(instance.result).to eql result
166
- end
167
- end
168
- end
169
-
170
- describe '#valid?' do
171
- let(:result) { double }
172
-
173
- it 'returns true' do
174
- expect(instance).to be_valid
175
- end
176
-
177
- context 'with an error' do
178
- include_context 'with an error'
179
-
180
- it 'returns true' do
181
- expect(instance).to be_valid
182
- end
183
- end
184
-
185
- context 'with a validator' do
186
- include_context 'with a validator'
187
-
188
- it 'returns false' do
189
- expect(instance).to_not be_valid
190
- end
191
-
192
- it 'does not duplicate errors on subsequent calls' do
193
- instance.valid?
194
- count = instance.errors.count
195
- instance.valid?
196
-
197
- expect(instance.errors.count).to eql count
198
- end
199
- end
200
- end
201
-
202
- describe '.run' do
203
- let(:outcome) { klass.run }
204
-
205
- it 'raises an error' do
206
- expect { outcome }.to raise_error NotImplementedError
207
- end
208
-
209
- context 'with #execute defined' do
210
- include_context 'with #execute defined'
211
-
212
- it 'returns an instance of Runnable' do
213
- expect(outcome).to be_a klass
214
- end
215
-
216
- it 'sets the result' do
217
- expect(outcome.result).to_not be_nil
218
- end
219
-
220
- context 'with a validator' do
221
- include_context 'with a validator'
222
-
223
- it 'returns an instance of Runnable' do
224
- expect(outcome).to be_a klass
225
- end
226
-
227
- it 'sets the result to nil' do
228
- expect(outcome.result).to be_nil
229
- end
230
- end
231
- end
232
-
233
- context 'caches the result of the run' do
234
- context 'when it is invalid' do
235
- let(:klass) do
236
- Class.new(ActiveInteraction::Base) do
237
- invalid = [false, true].cycle
238
-
239
- validate do |interaction|
240
- interaction.errors.add(:base, 'failed') unless invalid.next
241
- end
242
-
243
- def execute
244
- true
245
- end
246
- end
247
- end
248
-
249
- it 'fails' do
250
- expect(outcome).to_not be_valid
251
- expect(outcome.result).to be_nil
252
- expect(outcome).to_not be_valid
253
- expect(outcome.result).to be_nil
254
- end
255
- end
256
-
257
- context 'when it is valid' do
258
- let(:klass) do
259
- Class.new(ActiveInteraction::Base) do
260
- valid = [true, false].cycle
261
-
262
- validate do |interaction|
263
- interaction.errors.add(:base, 'failed') unless valid.next
264
- end
265
-
266
- def execute
267
- true
268
- end
269
- end
270
- end
271
-
272
- it 'succeeds' do
273
- expect(outcome).to be_valid
274
- expect(outcome.result).to be true
275
- expect(outcome).to be_valid
276
- expect(outcome.result).to be true
277
- end
278
- end
279
- end
280
-
281
- context 'with valid post-execution state' do
282
- before do
283
- klass.class_exec do
284
- attr_accessor :attribute
285
-
286
- validate { errors.add(:attribute) unless attribute }
287
-
288
- def execute
289
- self.attribute = true
290
- end
291
- end
292
- end
293
-
294
- it 'is invalid' do
295
- expect(outcome).to_not be_valid
296
- end
297
-
298
- it 'stays invalid' do
299
- outcome.attribute = false
300
- expect(outcome).to_not be_valid
301
- end
302
- end
303
-
304
- context 'with invalid post-execution state' do
305
- before do
306
- klass.class_exec do
307
- attr_accessor :attribute
308
-
309
- validate { errors.add(:attribute) if attribute }
310
-
311
- def execute
312
- self.attribute = true
313
- end
314
- end
315
- end
316
-
317
- it 'is valid' do
318
- expect(outcome).to be_valid
319
- end
320
-
321
- it 'stays valid' do
322
- outcome.attribute = true
323
- expect(outcome).to be_valid
324
- end
325
- end
326
-
327
- context 'with failing composition' do
328
- class CheckInnerForFailure # rubocop:disable Lint/ConstantDefinitionInBlock
329
- include ActiveInteraction::Runnable
330
-
331
- attr_reader :caught_error
332
-
333
- def execute
334
- compose(WrappableFailingInteraction)
335
- rescue StandardError
336
- @caught_error = true
337
- raise
338
- end
339
- end
340
-
341
- it 'throws an error from the inner interaction' do
342
- outcome = CheckInnerForFailure.run
343
- expect(outcome.caught_error).to be true
344
- end
345
- end
346
-
347
- context 'with block not called and error in execute around callback' do
348
- class CheckExecuteAroundCallbackForFailure # rubocop:disable Lint/ConstantDefinitionInBlock
349
- include ActiveInteraction::ActiveModelable
350
- include ActiveInteraction::Runnable
351
-
352
- set_callback :execute, :around, -> { errors.add(:base, 'invalid') }
353
-
354
- def execute
355
- true
356
- end
357
- end
358
-
359
- it 'is invalid' do
360
- outcome = CheckExecuteAroundCallbackForFailure.run
361
- expect(outcome).to_not be_valid
362
- end
363
- end
364
- end
365
-
366
- describe '.run!' do
367
- let(:result) { klass.run! }
368
-
369
- it 'raises an error' do
370
- expect { result }.to raise_error NotImplementedError
371
- end
372
-
373
- context 'with #execute defined' do
374
- include_context 'with #execute defined'
375
-
376
- it 'returns the result' do
377
- expect(result).to_not be_nil
378
- end
379
-
380
- context 'with a validator' do
381
- include_context 'with a validator'
382
-
383
- it 'raises an error' do
384
- expect do
385
- result
386
- end.to raise_error ActiveInteraction::InvalidInteractionError
387
- end
388
-
389
- it 'adds interaction instance to this error' do
390
- expect { result }.to raise_error do |error|
391
- expect(error.interaction).to be_a klass
392
- end
393
- end
394
- end
395
- end
396
- end
397
- end