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,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