active_interaction 5.2.0 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -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/version.rb +1 -1
  8. metadata +26 -112
  9. data/spec/active_interaction/array_input_spec.rb +0 -166
  10. data/spec/active_interaction/base_spec.rb +0 -537
  11. data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -45
  12. data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -49
  13. data/spec/active_interaction/concerns/hashable_spec.rb +0 -46
  14. data/spec/active_interaction/concerns/missable_spec.rb +0 -99
  15. data/spec/active_interaction/concerns/runnable_spec.rb +0 -397
  16. data/spec/active_interaction/errors_spec.rb +0 -196
  17. data/spec/active_interaction/filter/column_spec.rb +0 -87
  18. data/spec/active_interaction/filter_spec.rb +0 -60
  19. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +0 -13
  20. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +0 -13
  21. data/spec/active_interaction/filters/array_filter_spec.rb +0 -250
  22. data/spec/active_interaction/filters/boolean_filter_spec.rb +0 -87
  23. data/spec/active_interaction/filters/date_filter_spec.rb +0 -178
  24. data/spec/active_interaction/filters/date_time_filter_spec.rb +0 -189
  25. data/spec/active_interaction/filters/decimal_filter_spec.rb +0 -126
  26. data/spec/active_interaction/filters/file_filter_spec.rb +0 -40
  27. data/spec/active_interaction/filters/float_filter_spec.rb +0 -110
  28. data/spec/active_interaction/filters/hash_filter_spec.rb +0 -129
  29. data/spec/active_interaction/filters/integer_filter_spec.rb +0 -104
  30. data/spec/active_interaction/filters/interface_filter_spec.rb +0 -461
  31. data/spec/active_interaction/filters/object_filter_spec.rb +0 -237
  32. data/spec/active_interaction/filters/record_filter_spec.rb +0 -206
  33. data/spec/active_interaction/filters/string_filter_spec.rb +0 -60
  34. data/spec/active_interaction/filters/symbol_filter_spec.rb +0 -46
  35. data/spec/active_interaction/filters/time_filter_spec.rb +0 -251
  36. data/spec/active_interaction/grouped_input_spec.rb +0 -17
  37. data/spec/active_interaction/hash_input_spec.rb +0 -58
  38. data/spec/active_interaction/i18n_spec.rb +0 -113
  39. data/spec/active_interaction/inputs_spec.rb +0 -266
  40. data/spec/active_interaction/integration/array_interaction_spec.rb +0 -88
  41. data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -5
  42. data/spec/active_interaction/integration/date_interaction_spec.rb +0 -5
  43. data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -5
  44. data/spec/active_interaction/integration/file_interaction_spec.rb +0 -18
  45. data/spec/active_interaction/integration/float_interaction_spec.rb +0 -5
  46. data/spec/active_interaction/integration/hash_interaction_spec.rb +0 -76
  47. data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -5
  48. data/spec/active_interaction/integration/interface_interaction_spec.rb +0 -19
  49. data/spec/active_interaction/integration/object_interaction_spec.rb +0 -14
  50. data/spec/active_interaction/integration/record_integration_spec.rb +0 -5
  51. data/spec/active_interaction/integration/string_interaction_spec.rb +0 -5
  52. data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -5
  53. data/spec/active_interaction/integration/time_interaction_spec.rb +0 -88
  54. data/spec/active_interaction/modules/validation_spec.rb +0 -57
  55. data/spec/spec_helper.rb +0 -20
  56. data/spec/support/concerns.rb +0 -13
  57. data/spec/support/filters.rb +0 -227
  58. 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