active_interaction 4.0.5 → 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 +149 -6
  3. data/README.md +67 -32
  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 +36 -10
  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 -24
  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 +45 -95
  77. data/lib/active_interaction/filter_column.rb +0 -57
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'action_controller'
3
+ require 'active_support/core_ext/kernel/reporting'
3
4
 
4
5
  InteractionWithFilter = Class.new(TestInteraction) do
5
6
  float :thing
@@ -34,10 +35,10 @@ InterruptInteraction = Class.new(TestInteraction) do
34
35
  end
35
36
 
36
37
  describe ActiveInteraction::Base do
37
- include_context 'interactions'
38
-
39
38
  subject(:interaction) { described_class.new(inputs) }
40
39
 
40
+ include_context 'interactions'
41
+
41
42
  describe '.new(inputs = {})' do
42
43
  it 'does not set instance vars for reserved input names' do
43
44
  key = :execute
@@ -83,7 +84,7 @@ describe ActiveInteraction::Base do
83
84
  before { inputs[:thing] = 1 }
84
85
 
85
86
  it 'sets the attribute to the filtered value' do
86
- expect(interaction.thing).to eql 1.0
87
+ expect(interaction.thing).to be 1.0
87
88
  end
88
89
  end
89
90
 
@@ -181,8 +182,8 @@ describe ActiveInteraction::Base do
181
182
 
182
183
  it 'warns when redefining a filter' do
183
184
  klass = Class.new(described_class)
184
- expect(klass).to receive(:warn).with(/\AWARNING:/)
185
- klass.boolean :thing
185
+ allow(klass).to receive(:warn)
186
+ expect(klass.boolean(:thing)).to have_received(:warn).with(/\AWARNING:/)
186
187
  end
187
188
 
188
189
  describe '.run(inputs = {})' do
@@ -204,18 +205,18 @@ describe ActiveInteraction::Base do
204
205
  before { inputs[:thing] = thing }
205
206
 
206
207
  context 'failing runtime validations' do
207
- before do
208
- @execute = described_class.instance_method(:execute)
208
+ around do |example|
209
+ old_method = described_class.instance_method(:execute)
209
210
  described_class.send(:define_method, :execute) do
210
211
  errors.add(:thing, 'is invalid')
211
212
  errors.add(:thing, :invalid)
212
213
  true
213
214
  end
214
- end
215
215
 
216
- after do
216
+ example.run
217
+
217
218
  silence_warnings do
218
- described_class.send(:define_method, :execute, @execute)
219
+ described_class.send(:define_method, :execute, old_method)
219
220
  end
220
221
  end
221
222
 
@@ -279,7 +280,7 @@ describe ActiveInteraction::Base do
279
280
  let(:inputs) { { thing: 1, other: other_val } }
280
281
 
281
282
  it 'casts filtered inputs' do
282
- expect(interaction.inputs[:thing]).to eql 1.0
283
+ expect(interaction.inputs[:thing]).to be 1.0
283
284
  end
284
285
 
285
286
  it 'strips non-filtered inputs' do
@@ -332,232 +333,6 @@ describe ActiveInteraction::Base do
332
333
  end
333
334
  end
334
335
 
335
- describe '#given?' do
336
- let(:described_class) do
337
- Class.new(TestInteraction) do
338
- float :x,
339
- default: nil
340
-
341
- def execute
342
- given?(:x)
343
- end
344
- end
345
- end
346
-
347
- it 'is false when the input is not given' do
348
- expect(result).to be false
349
- end
350
-
351
- it 'is true when the input is nil' do
352
- inputs[:x] = nil
353
- expect(result).to be true
354
- end
355
-
356
- it 'is true when the input is given' do
357
- inputs[:x] = rand
358
- expect(result).to be true
359
- end
360
-
361
- it 'symbolizes its argument' do
362
- described_class.class_exec do
363
- def execute
364
- given?('x')
365
- end
366
- end
367
-
368
- inputs[:x] = rand
369
- expect(result).to be true
370
- end
371
-
372
- it 'only tracks inputs with filters' do
373
- described_class.class_exec do
374
- def execute
375
- given?(:y)
376
- end
377
- end
378
-
379
- inputs[:y] = rand
380
- expect(result).to be false
381
- end
382
-
383
- context 'nested hash values' do
384
- let(:described_class) do
385
- Class.new(TestInteraction) do
386
- hash :x, default: {} do
387
- boolean :y,
388
- default: true
389
- end
390
-
391
- def execute; end
392
- end
393
- end
394
-
395
- it 'is true when the nested inputs symbols are given' do
396
- described_class.class_exec do
397
- def execute
398
- given?(:x, :y)
399
- end
400
- end
401
-
402
- inputs[:x] = { y: false }
403
- expect(result).to be true
404
- end
405
-
406
- it 'is true when the nested inputs strings are given' do
407
- described_class.class_exec do
408
- def execute
409
- given?(:x, :y)
410
- end
411
- end
412
-
413
- inputs['x'] = { 'y' => false }
414
- expect(result).to be true
415
- end
416
-
417
- it 'is false when the nested input is not given' do
418
- described_class.class_exec do
419
- def execute
420
- given?(:x, :y)
421
- end
422
- end
423
-
424
- inputs[:x] = {}
425
- expect(result).to be false
426
- end
427
-
428
- it 'is false when the first input is not given' do
429
- described_class.class_exec do
430
- def execute
431
- given?(:x, :y)
432
- end
433
- end
434
-
435
- expect(result).to be false
436
- end
437
-
438
- it 'is false when the first input is nil' do
439
- described_class.class_exec do
440
- def execute
441
- given?(:x, :y)
442
- end
443
- end
444
-
445
- inputs[:x] = nil
446
- expect(result).to be false
447
- end
448
-
449
- it 'returns false if you go too far' do
450
- described_class.class_exec do
451
- def execute
452
- given?(:x, :y, :z)
453
- end
454
- end
455
-
456
- inputs[:x] = { y: true }
457
- expect(result).to be false
458
- end
459
- end
460
-
461
- context 'nested array values' do
462
- let(:described_class) do
463
- Class.new(TestInteraction) do
464
- array :x do
465
- hash do
466
- boolean :y, default: true
467
- end
468
- end
469
-
470
- def execute; end
471
- end
472
- end
473
-
474
- context 'has a positive index' do
475
- it 'returns true if found' do
476
- described_class.class_exec do
477
- def execute
478
- given?(:x, 0, :y)
479
- end
480
- end
481
-
482
- inputs[:x] = [{ y: true }]
483
- expect(result).to be true
484
- end
485
-
486
- it 'returns false if not found' do
487
- described_class.class_exec do
488
- def execute
489
- given?(:x, 0, :y)
490
- end
491
- end
492
-
493
- inputs[:x] = []
494
- expect(result).to be false
495
- end
496
- end
497
-
498
- context 'has a negative index' do
499
- it 'returns true if found' do
500
- described_class.class_exec do
501
- def execute
502
- given?(:x, -1, :y)
503
- end
504
- end
505
-
506
- inputs[:x] = [{ y: true }]
507
- expect(result).to be true
508
- end
509
-
510
- it 'returns false if not found' do
511
- described_class.class_exec do
512
- def execute
513
- given?(:x, -1, :y)
514
- end
515
- end
516
-
517
- inputs[:x] = []
518
- expect(result).to be false
519
- end
520
- end
521
-
522
- it 'returns false if you go too far' do
523
- described_class.class_exec do
524
- def execute
525
- given?(:x, 10, :y)
526
- end
527
- end
528
-
529
- inputs[:x] = [{}]
530
- expect(result).to be false
531
- end
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
559
- end
560
-
561
336
  context 'inheritance' do
562
337
  context 'filters' do
563
338
  let(:described_class) { InteractionWithFilter }
@@ -680,7 +455,7 @@ describe ActiveInteraction::Base do
680
455
  context 'callbacks' do
681
456
  let(:described_class) { Class.new(TestInteraction) }
682
457
 
683
- %w[type_check validate execute].each do |name|
458
+ %w[filter validate execute].each do |name|
684
459
  %w[before after around].map(&:to_sym).each do |type|
685
460
  it "runs the #{type} #{name} callback" do
686
461
  called = false
@@ -691,9 +466,9 @@ describe ActiveInteraction::Base do
691
466
  end
692
467
  end
693
468
 
694
- context 'with errors during type_check' do
469
+ context 'with errors during filter' do
695
470
  before do
696
- described_class.set_callback(:type_check, :before) do
471
+ described_class.set_callback(:filter, :before) do
697
472
  errors.add(:base)
698
473
  end
699
474
  end
@@ -15,19 +15,19 @@ shared_examples_for 'ActiveModel' do
15
15
  end
16
16
 
17
17
  describe ActiveInteraction::ActiveModelable do
18
- include_context 'concerns', ActiveInteraction::ActiveModelable
18
+ include_context 'concerns', described_class
19
19
 
20
20
  it_behaves_like 'ActiveModel'
21
21
 
22
22
  describe '.i18n_scope' do
23
23
  it 'returns the scope' do
24
- expect(klass.i18n_scope).to eql :active_interaction
24
+ expect(klass.i18n_scope).to be :active_interaction
25
25
  end
26
26
  end
27
27
 
28
28
  describe '#i18n_scope' do
29
29
  it 'returns the scope' do
30
- expect(instance.i18n_scope).to eql :active_interaction
30
+ expect(instance.i18n_scope).to be :active_interaction
31
31
  end
32
32
  end
33
33
 
@@ -23,27 +23,27 @@ describe ActiveInteraction::ActiveRecordable do
23
23
  context 'name is an input name' do
24
24
  let(:name) { described_class.filters.keys.first }
25
25
 
26
- it 'returns a FilterColumn' do
27
- expect(column).to be_a ActiveInteraction::FilterColumn
26
+ it 'returns a Filter::Column' do
27
+ expect(column).to be_a ActiveInteraction::Filter::Column
28
28
  end
29
29
 
30
- it 'returns a FilterColumn of type boolean' do
31
- expect(column.type).to eql :float
30
+ it 'returns a Filter::Column of type boolean' do
31
+ expect(column.type).to be :float
32
32
  end
33
33
  end
34
34
  end
35
35
 
36
36
  describe '#has_attribute?' do
37
37
  it 'returns true if the filter exists' do
38
- expect(outcome.has_attribute?(:thing)).to be_truthy
38
+ expect(outcome).to have_attribute(:thing)
39
39
  end
40
40
 
41
41
  it 'works with strings' do
42
- expect(outcome.has_attribute?('thing')).to be_truthy
42
+ expect(outcome).to have_attribute('thing')
43
43
  end
44
44
 
45
45
  it 'returns false if the filter does not exist' do
46
- expect(outcome.has_attribute?(:not_a_filter)).to be_falsey
46
+ expect(outcome).to_not have_attribute(:not_a_filter)
47
47
  end
48
48
  end
49
49
  end
@@ -1,11 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ActiveInteraction::Hashable do
4
- include_context 'concerns', ActiveInteraction::Hashable
4
+ include_context 'concerns', described_class
5
5
 
6
6
  describe '#hash(*args, &block)' do
7
7
  context 'with no arguments' do
8
- let(:hash) { subject.hash }
8
+ let(:hash) { instance.hash }
9
9
 
10
10
  it 'returns an Integer' do
11
11
  expect(hash).to be_an Integer
@@ -14,28 +14,28 @@ describe ActiveInteraction::Hashable do
14
14
 
15
15
  context 'with arguments' do
16
16
  let(:arguments) { [:attribute, {}] }
17
- let(:hash) { subject.hash(*arguments) }
17
+ let(:hash) { instance.hash(*arguments) }
18
18
 
19
- before { allow(subject).to receive(:method_missing) }
19
+ before { allow(instance).to receive(:method_missing) }
20
20
 
21
21
  it 'calls method_missing' do
22
22
  hash
23
- expect(subject).to have_received(:method_missing).once
23
+ expect(instance).to have_received(:method_missing).once
24
24
  .with(:hash, *arguments)
25
25
  end
26
26
 
27
27
  context 'with a block' do
28
28
  let(:block) { proc {} }
29
- let(:hash) { subject.hash(*arguments, &block) }
29
+ let(:hash) { instance.hash(*arguments, &block) }
30
30
 
31
31
  it 'calls method_missing' do
32
32
  hash
33
- expect(subject).to have_received(:method_missing).once
33
+ expect(instance).to have_received(:method_missing).once
34
34
  .with(:hash, *arguments)
35
35
  end
36
36
 
37
37
  it 'passes the block to method_missing' do
38
- allow(subject).to receive(:method_missing) do |*, &other_block|
38
+ allow(instance).to receive(:method_missing) do |*, &other_block|
39
39
  expect(other_block).to equal block
40
40
  end
41
41
  hash(&block)
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ActiveInteraction::Missable do
4
- include_context 'concerns', ActiveInteraction::Missable
4
+ include_context 'concerns', described_class
5
5
 
6
6
  describe '#respond_to?(slug, include_all = false)' do
7
7
  context 'with invalid slug' do
8
8
  let(:slug) { :slug }
9
9
 
10
10
  it 'returns false' do
11
- expect(instance.respond_to?(slug)).to be_falsey
11
+ expect(instance).to_not respond_to(slug)
12
12
  end
13
13
  end
14
14
 
@@ -16,7 +16,7 @@ describe ActiveInteraction::Missable do
16
16
  let(:slug) { :boolean }
17
17
 
18
18
  it 'returns true' do
19
- expect(instance.respond_to?(slug)).to be_truthy
19
+ expect(instance).to respond_to(slug)
20
20
  end
21
21
  end
22
22
  end
@@ -45,7 +45,7 @@ describe ActiveInteraction::Missable do
45
45
 
46
46
  it 'calls super' do
47
47
  expect do
48
- instance.method_missing(slug)
48
+ instance.public_send(slug)
49
49
  end.to raise_error NameError
50
50
  end
51
51
  end
@@ -55,12 +55,12 @@ describe ActiveInteraction::Missable do
55
55
  let(:slug) { :boolean }
56
56
 
57
57
  it 'returns self' do
58
- expect(instance.method_missing(slug)).to eql instance
58
+ expect(instance.public_send(slug)).to eql instance
59
59
  end
60
60
 
61
61
  it 'yields' do
62
62
  expect do |b|
63
- instance.method_missing(slug, &b)
63
+ instance.public_send(slug, &b)
64
64
  end.to yield_with_args(filter, [], {})
65
65
  end
66
66
 
@@ -69,7 +69,7 @@ describe ActiveInteraction::Missable do
69
69
 
70
70
  it 'yields' do
71
71
  expect do |b|
72
- instance.method_missing(:boolean, *names, &b)
72
+ instance.public_send(:boolean, *names, &b)
73
73
  end.to yield_with_args(filter, names, {})
74
74
  end
75
75
  end
@@ -79,7 +79,7 @@ describe ActiveInteraction::Missable do
79
79
 
80
80
  it 'yields' do
81
81
  expect do |b|
82
- instance.method_missing(:boolean, options, &b)
82
+ instance.public_send(:boolean, options, &b)
83
83
  end.to yield_with_args(filter, [], options)
84
84
  end
85
85
  end
@@ -90,7 +90,7 @@ describe ActiveInteraction::Missable do
90
90
 
91
91
  it 'yields' do
92
92
  expect do |b|
93
- instance.method_missing(:boolean, *names, options, &b)
93
+ instance.public_send(:boolean, *names, options, &b)
94
94
  end.to yield_with_args(filter, names, options)
95
95
  end
96
96
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ActiveInteraction::Runnable do
4
- include_context 'concerns', ActiveInteraction::Runnable
4
+ include_context 'concerns', described_class
5
5
 
6
6
  class WrappableFailingInteraction # rubocop:disable Lint/ConstantDefinitionInBlock
7
7
  include ActiveInteraction::Runnable
@@ -230,49 +230,51 @@ describe ActiveInteraction::Runnable do
230
230
  end
231
231
  end
232
232
 
233
- context 'caches the validity and result of the run' do
234
- let(:klass) do
235
- Class.new(ActiveInteraction::Base) do
236
- invalid = [false, true].cycle
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
237
238
 
238
- validate do |interaction|
239
- interaction.errors.add(:base, 'failed') unless invalid.next
240
- end
239
+ validate do |interaction|
240
+ interaction.errors.add(:base, 'failed') unless invalid.next
241
+ end
241
242
 
242
- def execute
243
- true
243
+ def execute
244
+ true
245
+ end
244
246
  end
245
247
  end
246
- end
247
248
 
248
- it 'is invalid' do
249
- expect(outcome).to_not be_valid
250
- expect(outcome.result).to be_nil
251
- expect(outcome).to_not be_valid
252
- expect(outcome.result).to be_nil
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
253
255
  end
254
- end
255
256
 
256
- context 'caches the validity and result of the run' do
257
- let(:klass) do
258
- Class.new(ActiveInteraction::Base) do
259
- valid = [true, false].cycle
257
+ context 'when it is valid' do
258
+ let(:klass) do
259
+ Class.new(ActiveInteraction::Base) do
260
+ valid = [true, false].cycle
260
261
 
261
- validate do |interaction|
262
- interaction.errors.add(:base, 'failed') unless valid.next
263
- end
262
+ validate do |interaction|
263
+ interaction.errors.add(:base, 'failed') unless valid.next
264
+ end
264
265
 
265
- def execute
266
- true
266
+ def execute
267
+ true
268
+ end
267
269
  end
268
270
  end
269
- end
270
271
 
271
- it 'is valid' do
272
- expect(outcome).to be_valid
273
- expect(outcome.result).to be true
274
- expect(outcome).to be_valid
275
- expect(outcome.result).to be true
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
276
278
  end
277
279
  end
278
280