active_interaction 5.3.0 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
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