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