active_interaction 3.7.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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