active_interaction 3.7.1 → 4.0.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +190 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +96 -90
  5. data/lib/active_interaction.rb +1 -7
  6. data/lib/active_interaction/base.rb +48 -33
  7. data/lib/active_interaction/concerns/active_modelable.rb +1 -3
  8. data/lib/active_interaction/concerns/active_recordable.rb +1 -6
  9. data/lib/active_interaction/concerns/hashable.rb +0 -1
  10. data/lib/active_interaction/concerns/missable.rb +0 -1
  11. data/lib/active_interaction/concerns/runnable.rb +16 -28
  12. data/lib/active_interaction/errors.rb +8 -7
  13. data/lib/active_interaction/filter.rb +51 -37
  14. data/lib/active_interaction/filter_column.rb +0 -3
  15. data/lib/active_interaction/filters/abstract_date_time_filter.rb +34 -36
  16. data/lib/active_interaction/filters/abstract_numeric_filter.rb +27 -17
  17. data/lib/active_interaction/filters/array_filter.rb +57 -36
  18. data/lib/active_interaction/filters/boolean_filter.rb +26 -12
  19. data/lib/active_interaction/filters/date_filter.rb +1 -2
  20. data/lib/active_interaction/filters/date_time_filter.rb +1 -2
  21. data/lib/active_interaction/filters/decimal_filter.rb +10 -28
  22. data/lib/active_interaction/filters/file_filter.rb +6 -5
  23. data/lib/active_interaction/filters/float_filter.rb +1 -2
  24. data/lib/active_interaction/filters/hash_filter.rb +37 -27
  25. data/lib/active_interaction/filters/integer_filter.rb +7 -8
  26. data/lib/active_interaction/filters/interface_filter.rb +48 -14
  27. data/lib/active_interaction/filters/object_filter.rb +23 -50
  28. data/lib/active_interaction/filters/record_filter.rb +10 -35
  29. data/lib/active_interaction/filters/string_filter.rb +21 -12
  30. data/lib/active_interaction/filters/symbol_filter.rb +13 -7
  31. data/lib/active_interaction/filters/time_filter.rb +19 -22
  32. data/lib/active_interaction/grouped_input.rb +0 -3
  33. data/lib/active_interaction/inputs.rb +89 -0
  34. data/lib/active_interaction/locale/ja.yml +24 -0
  35. data/lib/active_interaction/modules/input_processor.rb +9 -6
  36. data/lib/active_interaction/modules/validation.rb +9 -12
  37. data/lib/active_interaction/version.rb +1 -3
  38. data/spec/active_interaction/base_spec.rb +95 -35
  39. data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -2
  40. data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -2
  41. data/spec/active_interaction/concerns/hashable_spec.rb +1 -3
  42. data/spec/active_interaction/concerns/missable_spec.rb +0 -2
  43. data/spec/active_interaction/concerns/runnable_spec.rb +32 -12
  44. data/spec/active_interaction/errors_spec.rb +49 -22
  45. data/spec/active_interaction/filter_column_spec.rb +0 -2
  46. data/spec/active_interaction/filter_spec.rb +0 -2
  47. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +1 -3
  48. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -3
  49. data/spec/active_interaction/filters/array_filter_spec.rb +41 -15
  50. data/spec/active_interaction/filters/boolean_filter_spec.rb +58 -4
  51. data/spec/active_interaction/filters/date_filter_spec.rb +43 -3
  52. data/spec/active_interaction/filters/date_time_filter_spec.rb +43 -3
  53. data/spec/active_interaction/filters/decimal_filter_spec.rb +57 -3
  54. data/spec/active_interaction/filters/file_filter_spec.rb +1 -3
  55. data/spec/active_interaction/filters/float_filter_spec.rb +60 -4
  56. data/spec/active_interaction/filters/hash_filter_spec.rb +19 -9
  57. data/spec/active_interaction/filters/integer_filter_spec.rb +49 -7
  58. data/spec/active_interaction/filters/interface_filter_spec.rb +397 -24
  59. data/spec/active_interaction/filters/object_filter_spec.rb +23 -59
  60. data/spec/active_interaction/filters/record_filter_spec.rb +23 -49
  61. data/spec/active_interaction/filters/string_filter_spec.rb +15 -3
  62. data/spec/active_interaction/filters/symbol_filter_spec.rb +15 -3
  63. data/spec/active_interaction/filters/time_filter_spec.rb +65 -3
  64. data/spec/active_interaction/grouped_input_spec.rb +0 -2
  65. data/spec/active_interaction/i18n_spec.rb +3 -7
  66. data/spec/active_interaction/{modules/input_processor_spec.rb → inputs_spec.rb} +5 -5
  67. data/spec/active_interaction/integration/array_interaction_spec.rb +23 -12
  68. data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -2
  69. data/spec/active_interaction/integration/date_interaction_spec.rb +0 -2
  70. data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -2
  71. data/spec/active_interaction/integration/file_interaction_spec.rb +0 -2
  72. data/spec/active_interaction/integration/float_interaction_spec.rb +0 -2
  73. data/spec/active_interaction/integration/hash_interaction_spec.rb +0 -2
  74. data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -2
  75. data/spec/active_interaction/integration/interface_interaction_spec.rb +1 -3
  76. data/spec/active_interaction/integration/object_interaction_spec.rb +0 -2
  77. data/spec/active_interaction/integration/string_interaction_spec.rb +0 -2
  78. data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -2
  79. data/spec/active_interaction/integration/time_interaction_spec.rb +14 -18
  80. data/spec/active_interaction/modules/validation_spec.rb +1 -3
  81. data/spec/spec_helper.rb +2 -6
  82. data/spec/support/concerns.rb +0 -2
  83. data/spec/support/filters.rb +13 -9
  84. data/spec/support/interactions.rb +22 -14
  85. metadata +106 -52
  86. data/lib/active_interaction/backports.rb +0 -59
  87. data/lib/active_interaction/filters/abstract_filter.rb +0 -19
  88. data/spec/active_interaction/filters/abstract_filter_spec.rb +0 -8
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  shared_examples_for 'ActiveModel' do
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  InteractionWithFloatFilter = Class.new(TestInteraction) do
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::Hashable do
@@ -27,7 +25,7 @@ describe ActiveInteraction::Hashable do
27
25
  end
28
26
 
29
27
  context 'with a block' do
30
- let(:block) { proc {} }
28
+ let(:block) { proc {} } # rubocop:disable Lint/EmptyBlock
31
29
  let(:hash) { subject.hash(*arguments, &block) }
32
30
 
33
31
  it 'calls method_missing' do
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::Missable do
@@ -1,11 +1,9 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::Runnable do
6
4
  include_context 'concerns', ActiveInteraction::Runnable
7
5
 
8
- class WrappableFailingInteraction
6
+ class WrappableFailingInteraction # rubocop:disable Lint/ConstantDefinitionInBlock
9
7
  include ActiveInteraction::Runnable
10
8
 
11
9
  def execute
@@ -72,7 +70,7 @@ describe ActiveInteraction::Runnable do
72
70
  include_examples 'set_callback examples', :execute
73
71
 
74
72
  context 'execute with composed interaction' do
75
- class WithFailingCompose
73
+ class WithFailingCompose # rubocop:disable Lint/ConstantDefinitionInBlock
76
74
  include ActiveInteraction::Runnable
77
75
 
78
76
  def execute
@@ -107,11 +105,9 @@ describe ActiveInteraction::Runnable do
107
105
  context 'using if' do
108
106
  it 'yields errors to the if' do
109
107
  has_run = false
110
- # rubocop:disable Metic/LineLength
111
108
  WithFailingCompose.set_callback :execute, :after, if: -> { errors.any? } do
112
109
  has_run = true
113
110
  end
114
- # rubocop:enable Metic/LineLength
115
111
 
116
112
  WithFailingCompose.run
117
113
  expect(has_run).to be_truthy
@@ -237,10 +233,10 @@ describe ActiveInteraction::Runnable do
237
233
  context 'caches the validity and result of the run' do
238
234
  let(:klass) do
239
235
  Class.new(ActiveInteraction::Base) do
240
- INVALID = [false, true].cycle
236
+ invalid = [false, true].cycle
241
237
 
242
238
  validate do |interaction|
243
- interaction.errors.add(:base, 'failed') unless INVALID.next
239
+ interaction.errors.add(:base, 'failed') unless invalid.next
244
240
  end
245
241
 
246
242
  def execute
@@ -260,10 +256,10 @@ describe ActiveInteraction::Runnable do
260
256
  context 'caches the validity and result of the run' do
261
257
  let(:klass) do
262
258
  Class.new(ActiveInteraction::Base) do
263
- VALID = [true, false].cycle
259
+ valid = [true, false].cycle
264
260
 
265
261
  validate do |interaction|
266
- interaction.errors.add(:base, 'failed') unless VALID.next
262
+ interaction.errors.add(:base, 'failed') unless valid.next
267
263
  end
268
264
 
269
265
  def execute
@@ -327,14 +323,14 @@ describe ActiveInteraction::Runnable do
327
323
  end
328
324
 
329
325
  context 'with failing composition' do
330
- class CheckInnerForFailure
326
+ class CheckInnerForFailure # rubocop:disable Lint/ConstantDefinitionInBlock
331
327
  include ActiveInteraction::Runnable
332
328
 
333
329
  attr_reader :caught_error
334
330
 
335
331
  def execute
336
332
  compose(WrappableFailingInteraction)
337
- rescue
333
+ rescue StandardError
338
334
  @caught_error = true
339
335
  raise
340
336
  end
@@ -345,6 +341,24 @@ describe ActiveInteraction::Runnable do
345
341
  expect(outcome.caught_error).to be true
346
342
  end
347
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
348
362
  end
349
363
 
350
364
  describe '.run!' do
@@ -369,6 +383,12 @@ describe ActiveInteraction::Runnable do
369
383
  result
370
384
  end.to raise_error ActiveInteraction::InvalidInteractionError
371
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
372
392
  end
373
393
  end
374
394
  end
@@ -1,6 +1,15 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
2
+ require 'active_record'
3
+ unless defined?(JRUBY_VERSION) # rubocop:disable Style/IfUnlessModifier
4
+ require 'sqlite3'
5
+ end
6
+
7
+ unless defined?(JRUBY_VERSION)
8
+ ActiveRecord::Base.establish_connection(
9
+ adapter: 'sqlite3',
10
+ database: ':memory:'
11
+ )
12
+ end
4
13
 
5
14
  describe ActiveInteraction::Errors do
6
15
  let(:klass) do
@@ -17,25 +26,6 @@ describe ActiveInteraction::Errors do
17
26
 
18
27
  subject(:errors) { described_class.new(klass.new) }
19
28
 
20
- context 'backports' do
21
- describe '#delete' do
22
- it 'deletes the detailed error' do
23
- errors.add(:attribute)
24
- errors.delete(:attribute)
25
- expect(errors.details).to_not have_key :attribute
26
- end
27
- end
28
-
29
- describe '#initialize_dup' do
30
- it 'duplicates the detailed errors' do
31
- errors.add(:attribute)
32
- other = errors.dup
33
- expect(other.details).to eql errors.details
34
- expect(other.details).to_not be errors.details
35
- end
36
- end
37
- end
38
-
39
29
  describe '#merge!' do
40
30
  let(:other) { described_class.new(klass.new) }
41
31
 
@@ -117,7 +107,7 @@ describe ActiveInteraction::Errors do
117
107
  klass.name => {
118
108
  attributes: {
119
109
  attribute: {
120
- invalid_type: 'is not a valid %{type}'
110
+ invalid_type: 'is not a valid %<type>s'
121
111
  }
122
112
  }
123
113
  }
@@ -148,5 +138,42 @@ describe ActiveInteraction::Errors do
148
138
  expect(errors.messages[:base]).to include message
149
139
  end
150
140
  end
141
+
142
+ unless defined?(JRUBY_VERSION)
143
+ context 'with nested errors' do
144
+ before do
145
+ # suppress create_table output
146
+ allow($stdout).to receive(:puts)
147
+ ActiveRecord::Schema.define do
148
+ create_table(:as)
149
+ create_table(:bs) do |t|
150
+ t.column :a_id, :integer
151
+ t.column :name, :string
152
+ end
153
+ end
154
+
155
+ class A < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
156
+ has_one :b
157
+ accepts_nested_attributes_for :b
158
+ end
159
+
160
+ class B < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
161
+ belongs_to :a
162
+
163
+ validates :name, presence: true
164
+ end
165
+ end
166
+
167
+ let(:a) { A.create(b_attributes: { name: nil }) }
168
+
169
+ it 'merges the nested errors' do
170
+ a.valid?
171
+ expect(a.errors.messages).to eq('b.name': ["can't be blank"])
172
+ expect(a.errors.size).to eql 1
173
+ expect { errors.merge!(a.errors) }.to_not raise_error
174
+ expect(errors.size).to eql 1
175
+ end
176
+ end
177
+ end
151
178
  end
152
179
  end
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::FilterColumn do
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::Filter, :filter do
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::AbstractDateTimeFilter, :filter do
@@ -9,7 +7,7 @@ describe ActiveInteraction::AbstractDateTimeFilter, :filter do
9
7
  let(:value) { nil }
10
8
 
11
9
  it 'raises an error' do
12
- expect { filter.cast(value, nil) }.to raise_error NameError
10
+ expect { filter.send(:cast, value, nil) }.to raise_error NameError
13
11
  end
14
12
  end
15
13
  end
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::AbstractNumericFilter, :filter do
@@ -9,7 +7,7 @@ describe ActiveInteraction::AbstractNumericFilter, :filter do
9
7
  let(:value) { nil }
10
8
 
11
9
  it 'raises an error' do
12
- expect { filter.cast(value, nil) }.to raise_error NameError
10
+ expect { filter.send(:cast, value, nil) }.to raise_error NameError
13
11
  end
14
12
  end
15
13
  end
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::ArrayFilter, :filter do
@@ -36,7 +34,7 @@ describe ActiveInteraction::ArrayFilter, :filter do
36
34
  end
37
35
 
38
36
  describe '#cast' do
39
- let(:result) { filter.cast(value, nil) }
37
+ let(:result) { filter.send(:cast, value, nil) }
40
38
 
41
39
  context 'with an Array' do
42
40
  let(:value) { [] }
@@ -46,6 +44,20 @@ describe ActiveInteraction::ArrayFilter, :filter do
46
44
  end
47
45
  end
48
46
 
47
+ context 'with an implicit Array' do
48
+ let(:value) do
49
+ Class.new do
50
+ def to_ary
51
+ [1, 2, 3]
52
+ end
53
+ end.new
54
+ end
55
+
56
+ it 'returns the Array' do
57
+ expect(result).to eql value.to_ary
58
+ end
59
+ end
60
+
49
61
  context 'with a heterogenous Array' do
50
62
  let(:value) { [[], false, 0.0, {}, 0, '', :''] }
51
63
 
@@ -84,21 +96,35 @@ describe ActiveInteraction::ArrayFilter, :filter do
84
96
  end
85
97
  end
86
98
 
87
- context 'with a nested object filter' do
88
- let(:block) { proc { object } }
89
- let(:name) { :objects }
90
- let(:value) { [Object.new] }
99
+ [
100
+ %i[object class],
101
+ %i[record class],
102
+ %i[interface from]
103
+ ].each do |(type, option)|
104
+ context "with a nested #{type} filter" do
105
+ let(:block) { proc { public_send(type) } }
106
+ let(:name) { :objects }
107
+ let(:value) { [''] }
108
+
109
+ it 'does not raise an error' do
110
+ expect { result }.to_not raise_error
111
+ end
91
112
 
92
- it 'does not raise an error' do
93
- expect { result }.to_not raise_error
94
- end
113
+ it 'has a filter with the right key' do
114
+ expect(filter.filters).to have_key(:'0')
115
+ end
95
116
 
96
- it 'has a filter with the right key' do
97
- expect(filter.filters).to have_key(:object)
98
- end
117
+ it 'has a filter with the right option' do
118
+ expect(filter.filters[:'0'].options).to have_key(option)
119
+ end
120
+
121
+ context 'with a class set' do
122
+ let(:block) { proc { public_send(type, "#{option}": String) } }
99
123
 
100
- it 'has a filter with the right name' do
101
- expect(filter.filters[:object].name).to eql(:object)
124
+ it "does not override the #{option}" do
125
+ expect(filter.filters[:'0'].options[option]).to eql String
126
+ end
127
+ end
102
128
  end
103
129
  end
104
130
  end
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::BooleanFilter, :filter do
@@ -10,7 +8,21 @@ describe ActiveInteraction::BooleanFilter, :filter do
10
8
  context 'falsey' do
11
9
  [false, '0', 'false', 'FALSE', 'off', 'OFF'].each do |value|
12
10
  it "returns false for #{value.inspect}" do
13
- expect(filter.cast(value, nil)).to be_falsey
11
+ expect(filter.send(:cast, value, nil)).to be_falsey
12
+ end
13
+ end
14
+
15
+ context 'with an implicit string' do
16
+ let(:value) do
17
+ Class.new do
18
+ def to_str
19
+ 'false'
20
+ end
21
+ end.new
22
+ end
23
+
24
+ it 'returns false' do
25
+ expect(filter.send(:cast, value, nil)).to be_falsey
14
26
  end
15
27
  end
16
28
  end
@@ -18,7 +30,49 @@ describe ActiveInteraction::BooleanFilter, :filter do
18
30
  context 'truthy' do
19
31
  [true, '1', 'true', 'TRUE', 'on', 'ON'].each do |value|
20
32
  it "returns true for #{value.inspect}" do
21
- expect(filter.cast(value, nil)).to be_truthy
33
+ expect(filter.send(:cast, value, nil)).to be_truthy
34
+ end
35
+ end
36
+
37
+ context 'with an implicit string' do
38
+ let(:value) do
39
+ Class.new do
40
+ def to_str
41
+ 'true'
42
+ end
43
+ end.new
44
+ end
45
+
46
+ it 'returns true' do
47
+ expect(filter.send(:cast, value, nil)).to be_truthy
48
+ end
49
+ end
50
+ end
51
+
52
+ context 'with a blank String' do
53
+ let(:value) do
54
+ Class.new do
55
+ def to_str
56
+ ' '
57
+ end
58
+ end.new
59
+ end
60
+
61
+ context 'optional' do
62
+ include_context 'optional'
63
+
64
+ it 'returns the default' do
65
+ expect(filter.send(:cast, value, nil)).to eql options[:default]
66
+ end
67
+ end
68
+
69
+ context 'required' do
70
+ include_context 'required'
71
+
72
+ it 'raises an error' do
73
+ expect do
74
+ filter.send(:cast, value, nil)
75
+ end.to raise_error ActiveInteraction::MissingValueError
22
76
  end
23
77
  end
24
78
  end