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