active_interaction 5.3.0 → 5.4.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +2 -2
  4. data/lib/active_interaction/errors.rb +1 -1
  5. data/lib/active_interaction/version.rb +1 -1
  6. metadata +26 -112
  7. data/spec/active_interaction/array_input_spec.rb +0 -164
  8. data/spec/active_interaction/base_spec.rb +0 -536
  9. data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -43
  10. data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -47
  11. data/spec/active_interaction/concerns/hashable_spec.rb +0 -44
  12. data/spec/active_interaction/concerns/missable_spec.rb +0 -97
  13. data/spec/active_interaction/concerns/runnable_spec.rb +0 -395
  14. data/spec/active_interaction/errors_spec.rb +0 -223
  15. data/spec/active_interaction/filter/column_spec.rb +0 -85
  16. data/spec/active_interaction/filter_spec.rb +0 -58
  17. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +0 -11
  18. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +0 -11
  19. data/spec/active_interaction/filters/array_filter_spec.rb +0 -248
  20. data/spec/active_interaction/filters/boolean_filter_spec.rb +0 -85
  21. data/spec/active_interaction/filters/date_filter_spec.rb +0 -176
  22. data/spec/active_interaction/filters/date_time_filter_spec.rb +0 -187
  23. data/spec/active_interaction/filters/decimal_filter_spec.rb +0 -124
  24. data/spec/active_interaction/filters/file_filter_spec.rb +0 -38
  25. data/spec/active_interaction/filters/float_filter_spec.rb +0 -109
  26. data/spec/active_interaction/filters/hash_filter_spec.rb +0 -127
  27. data/spec/active_interaction/filters/integer_filter_spec.rb +0 -102
  28. data/spec/active_interaction/filters/interface_filter_spec.rb +0 -459
  29. data/spec/active_interaction/filters/object_filter_spec.rb +0 -235
  30. data/spec/active_interaction/filters/record_filter_spec.rb +0 -204
  31. data/spec/active_interaction/filters/string_filter_spec.rb +0 -58
  32. data/spec/active_interaction/filters/symbol_filter_spec.rb +0 -44
  33. data/spec/active_interaction/filters/time_filter_spec.rb +0 -249
  34. data/spec/active_interaction/grouped_input_spec.rb +0 -15
  35. data/spec/active_interaction/hash_input_spec.rb +0 -56
  36. data/spec/active_interaction/i18n_spec.rb +0 -111
  37. data/spec/active_interaction/inputs_spec.rb +0 -264
  38. data/spec/active_interaction/integration/array_interaction_spec.rb +0 -87
  39. data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -11
  40. data/spec/active_interaction/integration/date_interaction_spec.rb +0 -3
  41. data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -3
  42. data/spec/active_interaction/integration/file_interaction_spec.rb +0 -17
  43. data/spec/active_interaction/integration/float_interaction_spec.rb +0 -3
  44. data/spec/active_interaction/integration/hash_interaction_spec.rb +0 -106
  45. data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -3
  46. data/spec/active_interaction/integration/interface_interaction_spec.rb +0 -18
  47. data/spec/active_interaction/integration/object_interaction_spec.rb +0 -12
  48. data/spec/active_interaction/integration/record_integration_spec.rb +0 -3
  49. data/spec/active_interaction/integration/string_interaction_spec.rb +0 -3
  50. data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -3
  51. data/spec/active_interaction/integration/time_interaction_spec.rb +0 -86
  52. data/spec/active_interaction/modules/validation_spec.rb +0 -55
  53. data/spec/spec_helper.rb +0 -22
  54. data/spec/support/concerns.rb +0 -13
  55. data/spec/support/filters.rb +0 -227
  56. data/spec/support/interactions.rb +0 -124
@@ -1,536 +0,0 @@
1
- require 'action_controller'
2
- require 'active_support/core_ext/kernel/reporting'
3
-
4
- InteractionWithFilter = Class.new(TestInteraction) do
5
- float :thing
6
- end
7
-
8
- InteractionWithDateFilter = Class.new(TestInteraction) do
9
- date :thing
10
- end
11
-
12
- AddInteraction = Class.new(TestInteraction) do
13
- float :x, :y
14
-
15
- def execute
16
- x + y
17
- end
18
- end
19
-
20
- InterruptInteraction = Class.new(TestInteraction) do
21
- object :x, :z,
22
- class: Object,
23
- default: nil
24
-
25
- # NOTE: the relative position between this method
26
- # and the compose line should be preserved.
27
- def self.composition_location
28
- "#{__FILE__}:#{__LINE__ + 4}:in `execute'"
29
- end
30
-
31
- def execute
32
- compose(AddInteraction, x: x, y: z)
33
- end
34
- end
35
-
36
- RSpec.describe ActiveInteraction::Base do
37
- subject(:interaction) { described_class.new(inputs) }
38
-
39
- include_context 'interactions'
40
-
41
- describe '.new(inputs = {})' do
42
- it 'does not set instance vars for reserved input names' do
43
- key = :execute
44
- inputs[key] = nil
45
-
46
- expect(interaction.instance_variable_defined?(:"@#{key}")).to be false
47
- end
48
-
49
- context 'with a filter' do
50
- let(:described_class) { InteractionWithFilter }
51
-
52
- context 'validation' do
53
- context 'failing' do
54
- before { inputs[:thing] = thing }
55
-
56
- context 'with an invalid value' do
57
- let(:thing) { 'a' }
58
-
59
- it 'sets the attribute to the filtered value' do
60
- expect(interaction.thing).to equal thing
61
- end
62
- end
63
-
64
- context 'without a value' do
65
- let(:thing) { nil }
66
-
67
- it 'sets the attribute to the filtered value' do
68
- expect(interaction.thing).to equal thing
69
- end
70
- end
71
- end
72
-
73
- context 'passing' do
74
- before { inputs[:thing] = 1 }
75
-
76
- it 'returns a valid outcome' do
77
- expect(interaction).to be_valid
78
- end
79
- end
80
- end
81
-
82
- context 'with a single input' do
83
- before { inputs[:thing] = 1 }
84
-
85
- it 'sets the attribute to the filtered value' do
86
- expect(interaction.thing).to be 1.0
87
- end
88
- end
89
-
90
- context 'with multiple inputs' do
91
- let(:described_class) { InteractionWithDateFilter }
92
- let(:year) { 2012 }
93
- let(:month) { 1 }
94
- let(:day) { 2 }
95
-
96
- before do
97
- inputs.merge!(
98
- 'thing(1i)' => year.to_s,
99
- 'thing(2i)' => month.to_s,
100
- 'thing(3i)' => day.to_s
101
- )
102
- end
103
-
104
- it 'returns a Date' do
105
- expect(interaction.thing).to eql Date.new(year, month, day)
106
- end
107
- end
108
- end
109
- end
110
-
111
- describe '.desc' do
112
- let(:desc) { SecureRandom.hex }
113
-
114
- it 'returns nil' do
115
- expect(described_class.desc).to be_nil
116
- end
117
-
118
- context 'with a description' do
119
- it 'returns the description' do
120
- expect(described_class.desc(desc)).to eql desc
121
- end
122
-
123
- it 'saves the description' do
124
- described_class.desc(desc)
125
- expect(described_class.desc).to eql desc
126
- end
127
- end
128
- end
129
-
130
- describe '.method_missing(filter_type, *args, &block)' do
131
- it 'raises an error for an invalid filter type' do
132
- expect do
133
- Class.new(TestInteraction) do
134
- not_a_valid_filter_type :thing
135
- end
136
- end.to raise_error NoMethodError
137
- end
138
-
139
- it do
140
- expect do
141
- Class.new(TestInteraction) do
142
- float :_interaction_thing
143
- end
144
- end.to raise_error ActiveInteraction::InvalidFilterError
145
- end
146
-
147
- context 'with a filter' do
148
- let(:described_class) { InteractionWithFilter }
149
-
150
- it 'adds an attr_reader' do
151
- expect(interaction).to respond_to :thing
152
- end
153
-
154
- it 'adds an attr_writer' do
155
- expect(interaction).to respond_to :thing=
156
- end
157
- end
158
-
159
- context 'with multiple filters' do
160
- let(:described_class) do
161
- Class.new(TestInteraction) do
162
- float :thing1, :thing2
163
- end
164
- end
165
-
166
- %w[thing1 thing2].each do |thing|
167
- it "adds an attr_reader for #{thing}" do
168
- expect(interaction).to respond_to thing
169
- end
170
-
171
- it "adds an attr_writer for #{thing}" do
172
- expect(interaction).to respond_to "#{thing}="
173
- end
174
- end
175
- end
176
- end
177
-
178
- context 'with a filter' do
179
- let(:described_class) { InteractionWithFilter }
180
- let(:thing) { rand }
181
-
182
- it 'warns when redefining a filter' do
183
- klass = Class.new(described_class)
184
- allow(klass).to receive(:warn)
185
- expect(klass.boolean(:thing)).to have_received(:warn).with(/\AWARNING:/)
186
- end
187
-
188
- describe '.run(inputs = {})' do
189
- it "returns an instance of #{described_class}" do
190
- expect(outcome).to be_a described_class
191
- end
192
-
193
- context 'failing validations' do
194
- it 'returns an invalid outcome' do
195
- expect(outcome).to_not be_valid
196
- end
197
-
198
- it 'sets the result to nil' do
199
- expect(result).to be_nil
200
- end
201
- end
202
-
203
- context 'passing validations' do
204
- before { inputs[:thing] = thing }
205
-
206
- context 'failing runtime validations' do
207
- around do |example|
208
- old_method = described_class.instance_method(:execute)
209
- described_class.send(:define_method, :execute) do
210
- errors.add(:thing, 'is invalid')
211
- errors.add(:thing, :invalid)
212
- true
213
- end
214
-
215
- example.run
216
-
217
- silence_warnings do
218
- described_class.send(:define_method, :execute, old_method)
219
- end
220
- end
221
-
222
- it 'returns an invalid outcome' do
223
- expect(outcome).to be_invalid
224
- end
225
-
226
- it 'sets the result' do
227
- expect(result).to be true
228
- end
229
-
230
- it 'has errors' do
231
- expect(outcome.errors.messages[:thing]).to eql [
232
- 'is invalid',
233
- 'is invalid'
234
- ]
235
- end
236
-
237
- it 'has detailed errors' do
238
- expect(outcome.errors.details[:thing]).to eql [
239
- { error: 'is invalid' },
240
- { error: :invalid }
241
- ]
242
- end
243
- end
244
-
245
- it 'returns a valid outcome' do
246
- expect(outcome).to be_valid
247
- end
248
-
249
- it 'sets the result' do
250
- expect(result[:thing]).to eql thing
251
- end
252
- end
253
- end
254
-
255
- describe '.run!(inputs = {})' do
256
- subject(:result) { described_class.run!(inputs) }
257
-
258
- context 'failing validations' do
259
- it 'raises an error' do
260
- expect do
261
- result
262
- end.to raise_error ActiveInteraction::InvalidInteractionError
263
- end
264
- end
265
-
266
- context 'passing validations' do
267
- before { inputs[:thing] = thing }
268
-
269
- it 'returns the result' do
270
- expect(result[:thing]).to eql thing
271
- end
272
- end
273
- end
274
- end
275
-
276
- describe '#inputs' do
277
- let(:described_class) { InteractionWithFilter }
278
- let(:other_val) { SecureRandom.hex }
279
- let(:inputs) { { thing: 1, other: other_val } }
280
-
281
- it 'casts filtered inputs' do
282
- expect(interaction.inputs[:thing]).to be 1.0
283
- end
284
-
285
- it 'strips non-filtered inputs' do
286
- expect(interaction.inputs).to_not have_key(:other)
287
- end
288
- end
289
-
290
- describe '#compose' do
291
- let(:described_class) { InterruptInteraction }
292
- let(:x) { rand }
293
- let(:z) { rand }
294
-
295
- context 'with valid composition' do
296
- context 'when inputs is a hash' do
297
- let(:inputs) { { x: x, z: z } }
298
-
299
- it 'is valid' do
300
- expect(outcome).to be_valid
301
- end
302
-
303
- it 'returns the sum' do
304
- expect(result).to eql x + z
305
- end
306
- end
307
-
308
- context 'when inputs is an ActiveInteraction::Inputs' do
309
- let(:inputs) { ActiveInteraction::Inputs.new({ x: x, z: z }, described_class.new) }
310
-
311
- it 'is valid' do
312
- expect(outcome).to be_valid
313
- end
314
-
315
- it 'returns the sum' do
316
- expect(result).to eql x + z
317
- end
318
- end
319
- end
320
-
321
- context 'with invalid composition' do
322
- it 'is invalid' do
323
- expect(outcome).to be_invalid
324
- end
325
-
326
- it 'has the correct errors' do
327
- expect(outcome.errors.details)
328
- .to eql(x: [{ error: :missing }], base: [{ error: 'Y is required' }])
329
- end
330
-
331
- it 'has the correct backtrace' do
332
- described_class.run!(inputs)
333
- rescue ActiveInteraction::InvalidInteractionError => e
334
- expect(e.backtrace)
335
- .to include(InterruptInteraction.composition_location)
336
- end
337
- end
338
- end
339
-
340
- describe '#execute' do
341
- it 'raises an error' do
342
- expect { interaction.execute }.to raise_error NotImplementedError
343
- end
344
- end
345
-
346
- context 'inheritance' do
347
- context 'filters' do
348
- let(:described_class) { InteractionWithFilter }
349
-
350
- def filters(klass)
351
- klass.filters.keys
352
- end
353
-
354
- it 'includes the filters from the superclass' do
355
- expect(filters(Class.new(described_class))).to include :thing
356
- end
357
-
358
- it 'does not mutate the filters on the superclass' do
359
- Class.new(described_class) { float :other_thing }
360
-
361
- expect(filters(described_class)).to_not include :other_thing
362
- end
363
- end
364
-
365
- context 'validators' do
366
- it 'does not pollute validators' do
367
- a = Class.new(ActiveInteraction::Base) do
368
- string :a
369
- validates_presence_of :a
370
- end
371
-
372
- b = Class.new(ActiveInteraction::Base) do
373
- string :b
374
- validates_presence_of :b
375
- end
376
-
377
- expect(a.validators).to_not eql b.validators
378
- end
379
-
380
- it 'gives duped validators to subclasses' do
381
- a = Class.new(ActiveInteraction::Base) do
382
- string :a
383
- validates_presence_of :a
384
- end
385
-
386
- b = Class.new(a)
387
-
388
- expect(a.validators).to eql b.validators
389
- expect(a.validators).to_not equal b.validators
390
- end
391
- end
392
- end
393
-
394
- describe '.import_filters' do
395
- shared_context 'import_filters context' do |only, except|
396
- let(:klass) { AddInteraction }
397
-
398
- let(:described_class) do
399
- interaction = klass
400
- options = {}
401
- options[:only] = only unless only.nil?
402
- options[:except] = except unless except.nil?
403
-
404
- Class.new(TestInteraction) { import_filters interaction, options }
405
- end
406
- end
407
-
408
- shared_examples 'import_filters examples' do |only, except|
409
- include_context 'import_filters context', only, except
410
-
411
- it 'imports the filters' do
412
- expect(described_class.filters).to eql(
413
- klass.filters
414
- .select { |k, _| only.nil? ? true : [*only].include?(k) }
415
- .reject { |k, _| except.nil? ? false : [*except].include?(k) }
416
- )
417
- end
418
-
419
- it 'does not modify the source' do
420
- filters = klass.filters.dup
421
- described_class
422
- expect(klass.filters).to eql filters
423
- end
424
-
425
- it 'responds to readers and writers' do
426
- instance = described_class.new
427
-
428
- described_class.filters.each_key do |name|
429
- [name, "#{name}="].each do |method|
430
- expect(instance).to respond_to method
431
- end
432
- end
433
- end
434
- end
435
-
436
- context 'with neither :only nor :except' do
437
- include_examples 'import_filters examples', nil, nil
438
- end
439
-
440
- context 'with :only' do
441
- context 'as an Array' do
442
- include_examples 'import_filters examples', [:x], nil
443
- end
444
-
445
- context 'as an Symbol' do
446
- include_examples 'import_filters examples', :x, nil
447
- end
448
- end
449
-
450
- context 'with :except' do
451
- context 'as an Array' do
452
- include_examples 'import_filters examples', nil, [:x]
453
- end
454
-
455
- context 'as an Symbol' do
456
- include_examples 'import_filters examples', nil, :x
457
- end
458
- end
459
-
460
- context 'with :only & :except' do
461
- include_examples 'import_filters examples', [:x], [:x]
462
- end
463
- end
464
-
465
- context 'callbacks' do
466
- let(:described_class) { Class.new(TestInteraction) }
467
-
468
- %w[filter validate execute].each do |name|
469
- %w[before after around].map(&:to_sym).each do |type|
470
- it "runs the #{type} #{name} callback" do
471
- called = false
472
- described_class.set_callback(name, type) { called = true }
473
- outcome
474
- expect(called).to be_truthy
475
- end
476
- end
477
- end
478
-
479
- context 'with errors during filter' do
480
- before do
481
- described_class.set_callback(:filter, :before) do
482
- errors.add(:base)
483
- end
484
- end
485
-
486
- it 'is invalid' do
487
- expect(outcome).to be_invalid
488
- end
489
-
490
- it 'does not run validate callbacks' do
491
- called = false
492
- described_class.set_callback(:validate, :before) { called = true }
493
- outcome
494
- expect(called).to be_falsey
495
- end
496
-
497
- it 'does not run execute callbacks' do
498
- called = false
499
- described_class.set_callback(:execute, :before) { called = true }
500
- outcome
501
- expect(called).to be_falsey
502
- end
503
- end
504
-
505
- context 'with errors during validate' do
506
- before do
507
- described_class.set_callback(:validate, :before) do
508
- errors.add(:base)
509
- end
510
- end
511
-
512
- it 'is invalid' do
513
- expect(outcome).to be_invalid
514
- end
515
-
516
- it 'does not run execute callbacks' do
517
- called = false
518
- described_class.set_callback(:execute, :before) { called = true }
519
- outcome
520
- expect(called).to be_falsey
521
- end
522
- end
523
-
524
- context 'with errors during execute' do
525
- before do
526
- described_class.set_callback(:execute, :before) do
527
- errors.add(:base)
528
- end
529
- end
530
-
531
- it 'is invalid' do
532
- expect(outcome).to be_invalid
533
- end
534
- end
535
- end
536
- end
@@ -1,43 +0,0 @@
1
- RSpec.shared_examples_for 'ActiveModel' do
2
- it 'includes ActiveModel::Conversion' do
3
- expect(subject).to be_a_kind_of ActiveModel::Conversion
4
- end
5
-
6
- it 'includes ActiveModel::Validations' do
7
- expect(subject).to be_a_kind_of ActiveModel::Validations
8
- end
9
-
10
- it 'extends ActiveModel::Naming' do
11
- expect(subject.class).to be_a_kind_of ActiveModel::Naming
12
- end
13
- end
14
-
15
- RSpec.describe ActiveInteraction::ActiveModelable do
16
- include_context 'concerns', described_class
17
-
18
- it_behaves_like 'ActiveModel'
19
-
20
- describe '.i18n_scope' do
21
- it 'returns the scope' do
22
- expect(klass.i18n_scope).to be :active_interaction
23
- end
24
- end
25
-
26
- describe '#i18n_scope' do
27
- it 'returns the scope' do
28
- expect(instance.i18n_scope).to be :active_interaction
29
- end
30
- end
31
-
32
- describe '#new_record?' do
33
- it 'returns true' do
34
- expect(instance).to be_new_record
35
- end
36
- end
37
-
38
- describe '#persisted?' do
39
- it 'returns false' do
40
- expect(instance).to_not be_persisted
41
- end
42
- end
43
- end
@@ -1,47 +0,0 @@
1
- InteractionWithFloatFilter = Class.new(TestInteraction) do
2
- float :thing
3
- end
4
-
5
- RSpec.describe ActiveInteraction::ActiveRecordable do
6
- include_context 'interactions'
7
-
8
- let(:described_class) { InteractionWithFloatFilter }
9
-
10
- describe '#column_for_attribute(name)' do
11
- let(:column) { outcome.column_for_attribute(name) }
12
-
13
- context 'name is not an input name' do
14
- let(:name) { SecureRandom.hex }
15
-
16
- it 'returns nil if the attribute cannot be found' do
17
- expect(column).to be_nil
18
- end
19
- end
20
-
21
- context 'name is an input name' do
22
- let(:name) { described_class.filters.keys.first }
23
-
24
- it 'returns a Filter::Column' do
25
- expect(column).to be_a ActiveInteraction::Filter::Column
26
- end
27
-
28
- it 'returns a Filter::Column of type boolean' do
29
- expect(column.type).to be :float
30
- end
31
- end
32
- end
33
-
34
- describe '#has_attribute?' do
35
- it 'returns true if the filter exists' do
36
- expect(outcome).to have_attribute(:thing)
37
- end
38
-
39
- it 'works with strings' do
40
- expect(outcome).to have_attribute('thing')
41
- end
42
-
43
- it 'returns false if the filter does not exist' do
44
- expect(outcome).to_not have_attribute(:not_a_filter)
45
- end
46
- end
47
- end
@@ -1,44 +0,0 @@
1
- RSpec.describe ActiveInteraction::Hashable do
2
- include_context 'concerns', described_class
3
-
4
- describe '#hash(*args, &block)' do
5
- context 'with no arguments' do
6
- let(:hash) { instance.hash }
7
-
8
- it 'returns an Integer' do
9
- expect(hash).to be_an Integer
10
- end
11
- end
12
-
13
- context 'with arguments' do
14
- let(:arguments) { [:attribute, {}] }
15
- let(:hash) { instance.hash(*arguments) }
16
-
17
- before { allow(instance).to receive(:method_missing) }
18
-
19
- it 'calls method_missing' do
20
- hash
21
- expect(instance).to have_received(:method_missing).once
22
- .with(:hash, *arguments)
23
- end
24
-
25
- context 'with a block' do
26
- let(:block) { proc {} }
27
- let(:hash) { instance.hash(*arguments, &block) }
28
-
29
- it 'calls method_missing' do
30
- hash
31
- expect(instance).to have_received(:method_missing).once
32
- .with(:hash, *arguments)
33
- end
34
-
35
- it 'passes the block to method_missing' do
36
- allow(instance).to receive(:method_missing) do |*, &other_block|
37
- expect(other_block).to equal block
38
- end
39
- hash(&block)
40
- end
41
- end
42
- end
43
- end
44
- end