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,99 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActiveInteraction::Missable do
4
- include_context 'concerns', described_class
5
-
6
- describe '#respond_to?(slug, include_all = false)' do
7
- context 'with invalid slug' do
8
- let(:slug) { :slug }
9
-
10
- it 'returns false' do
11
- expect(instance).to_not respond_to(slug)
12
- end
13
- end
14
-
15
- context 'with valid slug' do
16
- let(:slug) { :boolean }
17
-
18
- it 'returns true' do
19
- expect(instance).to respond_to(slug)
20
- end
21
- end
22
- end
23
-
24
- describe '#method(sym)' do
25
- context 'with invalid slug' do
26
- let(:slug) { :slug }
27
-
28
- it 'returns false' do
29
- expect { instance.method(slug) }.to raise_error NameError
30
- end
31
- end
32
-
33
- context 'with valid slug' do
34
- let(:slug) { :boolean }
35
-
36
- it 'returns true' do
37
- expect(instance.method(slug)).to be_a Method
38
- end
39
- end
40
- end
41
-
42
- describe '#method_missing' do
43
- context 'with invalid slug' do
44
- let(:slug) { :slug }
45
-
46
- it 'calls super' do
47
- expect do
48
- instance.public_send(slug)
49
- end.to raise_error NameError
50
- end
51
- end
52
-
53
- context 'with valid slug' do
54
- let(:filter) { ActiveInteraction::Filter.factory(slug) }
55
- let(:slug) { :boolean }
56
-
57
- it 'returns self' do
58
- expect(instance.public_send(slug)).to eql instance
59
- end
60
-
61
- it 'yields' do
62
- expect do |b|
63
- instance.public_send(slug, &b)
64
- end.to yield_with_args(filter, [], {})
65
- end
66
-
67
- context 'with names' do
68
- let(:names) { %i[a b c] }
69
-
70
- it 'yields' do
71
- expect do |b|
72
- instance.public_send(:boolean, *names, &b)
73
- end.to yield_with_args(filter, names, {})
74
- end
75
- end
76
-
77
- context 'with options' do
78
- let(:options) { { a: nil, b: false, c: true } }
79
-
80
- it 'yields' do
81
- expect do |b|
82
- instance.public_send(:boolean, options, &b)
83
- end.to yield_with_args(filter, [], options)
84
- end
85
- end
86
-
87
- context 'with names & options' do
88
- let(:names) { %i[a b c] }
89
- let(:options) { { a: nil, b: false, c: true } }
90
-
91
- it 'yields' do
92
- expect do |b|
93
- instance.public_send(:boolean, *names, options, &b)
94
- end.to yield_with_args(filter, names, options)
95
- end
96
- end
97
- end
98
- end
99
- end
@@ -1,397 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActiveInteraction::Runnable do
4
- include_context 'concerns', described_class
5
-
6
- class WrappableFailingInteraction # rubocop:disable Lint/ConstantDefinitionInBlock
7
- include ActiveInteraction::Runnable
8
-
9
- def execute
10
- errors.add(:base)
11
- end
12
- end
13
-
14
- shared_context 'with an error' do
15
- before { instance.errors.add(:base) }
16
- end
17
-
18
- shared_context 'with a validator' do
19
- before { klass.validate { errors.add(:base) } }
20
- end
21
-
22
- shared_context 'with #execute defined' do
23
- before { klass.send(:define_method, :execute) { rand } }
24
- end
25
-
26
- context 'validations' do
27
- describe '#runtime_errors' do
28
- include_context 'with an error'
29
-
30
- it 'is invalid' do
31
- instance.result = nil
32
- expect(instance).to_not be_valid
33
- end
34
-
35
- it 'becomes valid if errors are cleared' do
36
- instance.result = nil
37
- instance.errors.clear
38
- instance.result = nil
39
- expect(instance).to be_valid
40
- end
41
- end
42
- end
43
-
44
- context 'callbacks' do
45
- describe '.set_callback' do
46
- include_context 'with #execute defined'
47
-
48
- shared_examples 'set_callback examples' do |name|
49
- context name do
50
- it 'does not raise an error' do
51
- expect do
52
- klass.set_callback name, :before, -> {}
53
- end.to_not raise_error
54
- end
55
-
56
- %i[after around before].each do |type|
57
- it type do
58
- has_run = false
59
-
60
- klass.set_callback name, type, -> { has_run = true }
61
-
62
- klass.run
63
- expect(has_run).to be_truthy
64
- end
65
- end
66
- end
67
- end
68
-
69
- include_examples 'set_callback examples', :validate
70
- include_examples 'set_callback examples', :execute
71
-
72
- context 'execute with composed interaction' do
73
- class WithFailingCompose # rubocop:disable Lint/ConstantDefinitionInBlock
74
- include ActiveInteraction::Runnable
75
-
76
- def execute
77
- compose(WrappableFailingInteraction)
78
- end
79
- end
80
-
81
- context 'around' do
82
- it 'is yielded errors from composed interactions' do
83
- block_result = nil
84
- WithFailingCompose.set_callback :execute, :around do |_, block|
85
- block_result = block.call
86
- end
87
-
88
- WithFailingCompose.run
89
- expect(block_result).to be_an(ActiveInteraction::Errors)
90
- expect(block_result).to include(:base)
91
- end
92
- end
93
-
94
- context 'after' do
95
- it 'is yielded errors from composed interactions' do
96
- has_run = false
97
- WithFailingCompose.set_callback :execute, :after do
98
- has_run = true
99
- end
100
-
101
- WithFailingCompose.run
102
- expect(has_run).to be_truthy
103
- end
104
-
105
- context 'using if' do
106
- it 'yields errors to the if' do
107
- has_run = false
108
- WithFailingCompose.set_callback :execute, :after, if: -> { errors.any? } do
109
- has_run = true
110
- end
111
-
112
- WithFailingCompose.run
113
- expect(has_run).to be_truthy
114
- end
115
- end
116
- end
117
- end
118
- end
119
- end
120
-
121
- describe '#errors' do
122
- it 'returns the errors' do
123
- expect(instance.errors).to be_an ActiveInteraction::Errors
124
- end
125
- end
126
-
127
- describe '#execute' do
128
- it 'raises an error' do
129
- expect { instance.execute }.to raise_error NotImplementedError
130
- end
131
- end
132
-
133
- describe '#result' do
134
- it 'returns the result' do
135
- expect(instance.result).to be_nil
136
- end
137
- end
138
-
139
- describe '#result=' do
140
- let(:result) { double }
141
-
142
- it 'returns the result' do
143
- expect((instance.result = result)).to eql result
144
- end
145
-
146
- it 'sets the result' do
147
- instance.result = result
148
- expect(instance.result).to eql result
149
- end
150
-
151
- context 'with an error' do
152
- include_context 'with an error'
153
-
154
- it 'sets the result' do
155
- instance.result = result
156
- expect(instance.result).to eql result
157
- end
158
- end
159
-
160
- context 'with a validator' do
161
- include_context 'with a validator'
162
-
163
- it 'sets the result' do
164
- instance.result = result
165
- expect(instance.result).to eql result
166
- end
167
- end
168
- end
169
-
170
- describe '#valid?' do
171
- let(:result) { double }
172
-
173
- it 'returns true' do
174
- expect(instance).to be_valid
175
- end
176
-
177
- context 'with an error' do
178
- include_context 'with an error'
179
-
180
- it 'returns true' do
181
- expect(instance).to be_valid
182
- end
183
- end
184
-
185
- context 'with a validator' do
186
- include_context 'with a validator'
187
-
188
- it 'returns false' do
189
- expect(instance).to_not be_valid
190
- end
191
-
192
- it 'does not duplicate errors on subsequent calls' do
193
- instance.valid?
194
- count = instance.errors.count
195
- instance.valid?
196
-
197
- expect(instance.errors.count).to eql count
198
- end
199
- end
200
- end
201
-
202
- describe '.run' do
203
- let(:outcome) { klass.run }
204
-
205
- it 'raises an error' do
206
- expect { outcome }.to raise_error NotImplementedError
207
- end
208
-
209
- context 'with #execute defined' do
210
- include_context 'with #execute defined'
211
-
212
- it 'returns an instance of Runnable' do
213
- expect(outcome).to be_a klass
214
- end
215
-
216
- it 'sets the result' do
217
- expect(outcome.result).to_not be_nil
218
- end
219
-
220
- context 'with a validator' do
221
- include_context 'with a validator'
222
-
223
- it 'returns an instance of Runnable' do
224
- expect(outcome).to be_a klass
225
- end
226
-
227
- it 'sets the result to nil' do
228
- expect(outcome.result).to be_nil
229
- end
230
- end
231
- end
232
-
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
238
-
239
- validate do |interaction|
240
- interaction.errors.add(:base, 'failed') unless invalid.next
241
- end
242
-
243
- def execute
244
- true
245
- end
246
- end
247
- end
248
-
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
255
- end
256
-
257
- context 'when it is valid' do
258
- let(:klass) do
259
- Class.new(ActiveInteraction::Base) do
260
- valid = [true, false].cycle
261
-
262
- validate do |interaction|
263
- interaction.errors.add(:base, 'failed') unless valid.next
264
- end
265
-
266
- def execute
267
- true
268
- end
269
- end
270
- end
271
-
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
278
- end
279
- end
280
-
281
- context 'with valid post-execution state' do
282
- before do
283
- klass.class_exec do
284
- attr_accessor :attribute
285
-
286
- validate { errors.add(:attribute) unless attribute }
287
-
288
- def execute
289
- self.attribute = true
290
- end
291
- end
292
- end
293
-
294
- it 'is invalid' do
295
- expect(outcome).to_not be_valid
296
- end
297
-
298
- it 'stays invalid' do
299
- outcome.attribute = false
300
- expect(outcome).to_not be_valid
301
- end
302
- end
303
-
304
- context 'with invalid post-execution state' do
305
- before do
306
- klass.class_exec do
307
- attr_accessor :attribute
308
-
309
- validate { errors.add(:attribute) if attribute }
310
-
311
- def execute
312
- self.attribute = true
313
- end
314
- end
315
- end
316
-
317
- it 'is valid' do
318
- expect(outcome).to be_valid
319
- end
320
-
321
- it 'stays valid' do
322
- outcome.attribute = true
323
- expect(outcome).to be_valid
324
- end
325
- end
326
-
327
- context 'with failing composition' do
328
- class CheckInnerForFailure # rubocop:disable Lint/ConstantDefinitionInBlock
329
- include ActiveInteraction::Runnable
330
-
331
- attr_reader :caught_error
332
-
333
- def execute
334
- compose(WrappableFailingInteraction)
335
- rescue StandardError
336
- @caught_error = true
337
- raise
338
- end
339
- end
340
-
341
- it 'throws an error from the inner interaction' do
342
- outcome = CheckInnerForFailure.run
343
- expect(outcome.caught_error).to be true
344
- end
345
- end
346
-
347
- context 'with block not called and error in execute around callback' do
348
- class CheckExecuteAroundCallbackForFailure # rubocop:disable Lint/ConstantDefinitionInBlock
349
- include ActiveInteraction::ActiveModelable
350
- include ActiveInteraction::Runnable
351
-
352
- set_callback :execute, :around, -> { errors.add(:base, 'invalid') }
353
-
354
- def execute
355
- true
356
- end
357
- end
358
-
359
- it 'is invalid' do
360
- outcome = CheckExecuteAroundCallbackForFailure.run
361
- expect(outcome).to_not be_valid
362
- end
363
- end
364
- end
365
-
366
- describe '.run!' do
367
- let(:result) { klass.run! }
368
-
369
- it 'raises an error' do
370
- expect { result }.to raise_error NotImplementedError
371
- end
372
-
373
- context 'with #execute defined' do
374
- include_context 'with #execute defined'
375
-
376
- it 'returns the result' do
377
- expect(result).to_not be_nil
378
- end
379
-
380
- context 'with a validator' do
381
- include_context 'with a validator'
382
-
383
- it 'raises an error' do
384
- expect do
385
- result
386
- end.to raise_error ActiveInteraction::InvalidInteractionError
387
- end
388
-
389
- it 'adds interaction instance to this error' do
390
- expect { result }.to raise_error do |error|
391
- expect(error.interaction).to be_a klass
392
- end
393
- end
394
- end
395
- end
396
- end
397
- end
@@ -1,196 +0,0 @@
1
- require 'spec_helper'
2
- require 'active_record'
3
- require 'sqlite3'
4
-
5
- ActiveRecord::Base.establish_connection(
6
- adapter: 'sqlite3',
7
- database: ':memory:'
8
- )
9
-
10
- describe ActiveInteraction::Errors do
11
- subject(:errors) { described_class.new(klass.new) }
12
-
13
- let(:klass) do
14
- Class.new(ActiveInteraction::Base) do
15
- string :attribute, defualt: nil
16
- array :array, defualt: nil
17
-
18
- def self.name
19
- @name ||= SecureRandom.hex
20
- end
21
- end
22
- end
23
-
24
- describe '#merge!' do
25
- let(:other) { described_class.new(klass.new) }
26
-
27
- context 'with an error' do
28
- before do
29
- other.add(:attribute)
30
- end
31
-
32
- it 'adds the error' do
33
- errors.merge!(other)
34
- expect(errors.messages[:attribute]).to eql ['is invalid']
35
- end
36
-
37
- it 'does not add duplicate errors' do
38
- other.add(:attribute)
39
- errors.merge!(other)
40
- expect(errors.messages[:attribute]).to eql ['is invalid']
41
- end
42
- end
43
-
44
- context 'with a detailed error' do
45
- context 'that is a symbol' do
46
- before do
47
- other.add(:attribute)
48
- end
49
-
50
- it 'adds the error' do
51
- errors.merge!(other)
52
- expect(errors.details[:attribute]).to eql [{ error: :invalid }]
53
- end
54
- end
55
-
56
- context 'that is a symbol on base' do
57
- before do
58
- other.add(:base)
59
- end
60
-
61
- it 'adds the error' do
62
- errors.merge!(other)
63
- expect(errors.details[:base]).to eql [{ error: :invalid }]
64
- end
65
- end
66
-
67
- context 'that is a string' do
68
- let(:message) { SecureRandom.hex }
69
-
70
- before do
71
- other.add(:base, message)
72
- end
73
-
74
- it 'adds the error' do
75
- errors.merge!(other)
76
- expect(errors.details[:base]).to eql [{ error: message }]
77
- end
78
- end
79
-
80
- context 'that uses the :message option' do
81
- let(:message) { SecureRandom.hex }
82
- let(:error_name) { :some_error }
83
-
84
- before do
85
- other.add(:base, error_name, message: message)
86
- end
87
-
88
- it 'adds the error' do
89
- errors.merge!(other)
90
- expect(errors.details[:base]).to eql [{ error: error_name }]
91
- expect(errors.messages[:base]).to eql [message]
92
- end
93
- end
94
- end
95
-
96
- context 'with an interpolated detailed error' do
97
- before do
98
- I18n.backend.store_translations('en',
99
- activemodel: {
100
- errors: {
101
- models: {
102
- klass.name => {
103
- attributes: {
104
- attribute: {
105
- invalid_type: 'is not a valid %<type>s'
106
- }
107
- }
108
- }
109
- }
110
- }
111
- }
112
- )
113
-
114
- other.add(:attribute, :invalid_type, type: nil)
115
- end
116
-
117
- it 'does not raise an error' do
118
- expect { errors.merge!(other) }.to_not raise_error
119
- end
120
- end
121
-
122
- context 'with nested index errors' do
123
- let(:other) { described_class.new(klass.new) }
124
-
125
- before do
126
- if ActiveRecord.respond_to?(:index_nested_attribute_errors)
127
- allow(ActiveRecord).to receive(:index_nested_attribute_errors).and_return(true)
128
- else
129
- allow(ActiveRecord::Base).to receive(:index_nested_attribute_errors).and_return(true)
130
- end
131
-
132
- other.add(:'array[0]')
133
- end
134
-
135
- it 'adds the error' do
136
- errors.merge!(other)
137
- expect(errors.messages[:'array[0]']).to eql ['is invalid']
138
- end
139
- end
140
-
141
- context 'with ActiveModel errors' do
142
- let(:other) { ActiveModel::Errors.new(klass.new) }
143
-
144
- it 'does not raise an error' do
145
- expect { errors.merge!(other) }.to_not raise_error
146
- end
147
-
148
- it 'merges messages' do
149
- message = SecureRandom.hex
150
- other.add(:base, message)
151
- errors.merge!(other)
152
- expect(errors.messages[:base]).to include message
153
- end
154
- end
155
-
156
- context 'with nested errors' do
157
- let(:a_klass) do
158
- Class.new(ActiveRecord::Base) do
159
- has_one :b
160
- accepts_nested_attributes_for :b
161
- end
162
- end
163
- let(:a) { A.create(b_attributes: { name: nil }) }
164
- let(:b_klass) do
165
- Class.new(ActiveRecord::Base) do
166
- belongs_to :a
167
-
168
- validates :name, presence: true
169
- end
170
- end
171
-
172
- before do
173
- # suppress create_table output
174
- allow($stdout).to receive(:puts)
175
- ActiveRecord::Schema.define do
176
- create_table(:as)
177
- create_table(:bs) do |t|
178
- t.column :a_id, :integer
179
- t.column :name, :string
180
- end
181
- end
182
-
183
- stub_const('A', a_klass)
184
- stub_const('B', b_klass)
185
- end
186
-
187
- it 'merges the nested errors' do
188
- a.valid?
189
- expect(a.errors.messages).to eq('b.name': ["can't be blank"])
190
- expect(a.errors.size).to be 1
191
- expect { errors.merge!(a.errors) }.to_not raise_error
192
- expect(errors.size).to be 1
193
- end
194
- end
195
- end
196
- end