active_interaction 3.8.3 → 4.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +193 -0
  3. data/README.md +97 -116
  4. data/lib/active_interaction.rb +2 -7
  5. data/lib/active_interaction/base.rb +44 -67
  6. data/lib/active_interaction/concerns/active_modelable.rb +1 -3
  7. data/lib/active_interaction/concerns/active_recordable.rb +1 -6
  8. data/lib/active_interaction/concerns/hashable.rb +0 -1
  9. data/lib/active_interaction/concerns/missable.rb +0 -1
  10. data/lib/active_interaction/concerns/runnable.rb +6 -12
  11. data/lib/active_interaction/errors.rb +4 -19
  12. data/lib/active_interaction/filter.rb +66 -37
  13. data/lib/active_interaction/filter_column.rb +0 -3
  14. data/lib/active_interaction/filters/abstract_date_time_filter.rb +38 -36
  15. data/lib/active_interaction/filters/abstract_numeric_filter.rb +27 -17
  16. data/lib/active_interaction/filters/array_filter.rb +59 -36
  17. data/lib/active_interaction/filters/boolean_filter.rb +26 -12
  18. data/lib/active_interaction/filters/date_filter.rb +1 -2
  19. data/lib/active_interaction/filters/date_time_filter.rb +1 -2
  20. data/lib/active_interaction/filters/decimal_filter.rb +10 -28
  21. data/lib/active_interaction/filters/file_filter.rb +6 -5
  22. data/lib/active_interaction/filters/float_filter.rb +1 -2
  23. data/lib/active_interaction/filters/hash_filter.rb +37 -27
  24. data/lib/active_interaction/filters/integer_filter.rb +7 -8
  25. data/lib/active_interaction/filters/interface_filter.rb +48 -14
  26. data/lib/active_interaction/filters/object_filter.rb +23 -50
  27. data/lib/active_interaction/filters/record_filter.rb +10 -35
  28. data/lib/active_interaction/filters/string_filter.rb +21 -12
  29. data/lib/active_interaction/filters/symbol_filter.rb +13 -7
  30. data/lib/active_interaction/filters/time_filter.rb +24 -19
  31. data/lib/active_interaction/grouped_input.rb +0 -3
  32. data/lib/active_interaction/inputs.rb +120 -0
  33. data/lib/active_interaction/modules/validation.rb +9 -21
  34. data/lib/active_interaction/version.rb +1 -3
  35. data/spec/active_interaction/base_spec.rb +38 -99
  36. data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -2
  37. data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -2
  38. data/spec/active_interaction/concerns/hashable_spec.rb +0 -2
  39. data/spec/active_interaction/concerns/missable_spec.rb +0 -2
  40. data/spec/active_interaction/concerns/runnable_spec.rb +9 -13
  41. data/spec/active_interaction/errors_spec.rb +4 -25
  42. data/spec/active_interaction/filter_column_spec.rb +0 -2
  43. data/spec/active_interaction/filter_spec.rb +0 -2
  44. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +1 -3
  45. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -3
  46. data/spec/active_interaction/filters/array_filter_spec.rb +62 -13
  47. data/spec/active_interaction/filters/boolean_filter_spec.rb +58 -4
  48. data/spec/active_interaction/filters/date_filter_spec.rb +43 -3
  49. data/spec/active_interaction/filters/date_time_filter_spec.rb +43 -3
  50. data/spec/active_interaction/filters/decimal_filter_spec.rb +57 -3
  51. data/spec/active_interaction/filters/file_filter_spec.rb +1 -3
  52. data/spec/active_interaction/filters/float_filter_spec.rb +60 -4
  53. data/spec/active_interaction/filters/hash_filter_spec.rb +19 -9
  54. data/spec/active_interaction/filters/integer_filter_spec.rb +49 -7
  55. data/spec/active_interaction/filters/interface_filter_spec.rb +397 -24
  56. data/spec/active_interaction/filters/object_filter_spec.rb +23 -59
  57. data/spec/active_interaction/filters/record_filter_spec.rb +23 -49
  58. data/spec/active_interaction/filters/string_filter_spec.rb +15 -3
  59. data/spec/active_interaction/filters/symbol_filter_spec.rb +15 -3
  60. data/spec/active_interaction/filters/time_filter_spec.rb +65 -3
  61. data/spec/active_interaction/grouped_input_spec.rb +0 -2
  62. data/spec/active_interaction/i18n_spec.rb +3 -7
  63. data/spec/active_interaction/{modules/input_processor_spec.rb → inputs_spec.rb} +35 -5
  64. data/spec/active_interaction/integration/array_interaction_spec.rb +18 -22
  65. data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -2
  66. data/spec/active_interaction/integration/date_interaction_spec.rb +0 -2
  67. data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -2
  68. data/spec/active_interaction/integration/file_interaction_spec.rb +0 -2
  69. data/spec/active_interaction/integration/float_interaction_spec.rb +0 -2
  70. data/spec/active_interaction/integration/hash_interaction_spec.rb +1 -3
  71. data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -2
  72. data/spec/active_interaction/integration/interface_interaction_spec.rb +1 -3
  73. data/spec/active_interaction/integration/object_interaction_spec.rb +0 -2
  74. data/spec/active_interaction/integration/string_interaction_spec.rb +0 -2
  75. data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -2
  76. data/spec/active_interaction/integration/time_interaction_spec.rb +14 -18
  77. data/spec/active_interaction/modules/validation_spec.rb +1 -3
  78. data/spec/spec_helper.rb +2 -12
  79. data/spec/support/concerns.rb +0 -2
  80. data/spec/support/filters.rb +13 -9
  81. data/spec/support/interactions.rb +22 -14
  82. metadata +92 -62
  83. data/lib/active_interaction/backports.rb +0 -59
  84. data/lib/active_interaction/filters/abstract_filter.rb +0 -19
  85. data/lib/active_interaction/modules/input_processor.rb +0 -52
  86. 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
@@ -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 Metrics/LineLength
111
108
  WithFailingCompose.set_callback :execute, :after, if: -> { errors.any? } do
112
109
  has_run = true
113
110
  end
114
- # rubocop:enable Metrics/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
@@ -347,7 +343,7 @@ describe ActiveInteraction::Runnable do
347
343
  end
348
344
 
349
345
  context 'with block not called and error in execute around callback' do
350
- class CheckExecuteAroundCallbackForFailure
346
+ class CheckExecuteAroundCallbackForFailure # rubocop:disable Lint/ConstantDefinitionInBlock
351
347
  include ActiveInteraction::ActiveModelable
352
348
  include ActiveInteraction::Runnable
353
349
 
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
  require 'active_record'
5
3
  unless defined?(JRUBY_VERSION) # rubocop:disable Style/IfUnlessModifier
@@ -28,25 +26,6 @@ describe ActiveInteraction::Errors do
28
26
 
29
27
  subject(:errors) { described_class.new(klass.new) }
30
28
 
31
- context 'backports' do
32
- describe '#delete' do
33
- it 'deletes the detailed error' do
34
- errors.add(:attribute)
35
- errors.delete(:attribute)
36
- expect(errors.details).to_not have_key :attribute
37
- end
38
- end
39
-
40
- describe '#initialize_dup' do
41
- it 'duplicates the detailed errors' do
42
- errors.add(:attribute)
43
- other = errors.dup
44
- expect(other.details).to eql errors.details
45
- expect(other.details).to_not be errors.details
46
- end
47
- end
48
- end
49
-
50
29
  describe '#merge!' do
51
30
  let(:other) { described_class.new(klass.new) }
52
31
 
@@ -128,7 +107,7 @@ describe ActiveInteraction::Errors do
128
107
  klass.name => {
129
108
  attributes: {
130
109
  attribute: {
131
- invalid_type: 'is not a valid %{type}'
110
+ invalid_type: 'is not a valid %<type>s'
132
111
  }
133
112
  }
134
113
  }
@@ -173,12 +152,12 @@ describe ActiveInteraction::Errors do
173
152
  end
174
153
  end
175
154
 
176
- class A < ActiveRecord::Base
155
+ class A < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
177
156
  has_one :b
178
157
  accepts_nested_attributes_for :b
179
158
  end
180
159
 
181
- class B < ActiveRecord::Base
160
+ class B < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
182
161
  belongs_to :a
183
162
 
184
163
  validates :name, presence: true
@@ -189,7 +168,7 @@ describe ActiveInteraction::Errors do
189
168
 
190
169
  it 'merges the nested errors' do
191
170
  a.valid?
192
- expect(a.errors.messages).to eql(:'b.name' => ["can't be blank"])
171
+ expect(a.errors.messages).to eq('b.name': ["can't be blank"])
193
172
  expect(a.errors.size).to eql 1
194
173
  expect { errors.merge!(a.errors) }.to_not raise_error
195
174
  expect(errors.size).to eql 1
@@ -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,58 @@ 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
112
+
113
+ it 'has a filter with the right key' do
114
+ expect(filter.filters).to have_key(:'0')
115
+ end
91
116
 
92
- it 'does not raise an error' do
93
- expect { result }.to_not raise_error
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) } }
123
+
124
+ it "does not override the #{option}" do
125
+ expect(filter.filters[:'0'].options[option]).to eql String
126
+ end
127
+ end
94
128
  end
129
+ end
95
130
 
96
- it 'has a filter with the right key' do
97
- expect(filter.filters).to have_key(:object)
131
+ context 'with a nested interface type' do
132
+ context 'with the methods option set' do
133
+ let(:block) { proc { public_send(:interface, methods: %i[to_s]) } }
134
+
135
+ it 'has a filter with the right option' do
136
+ expect(filter.filters[:'0'].options).to have_key(:methods)
137
+ expect(filter.filters[:'0'].options[:methods]).to eql %i[to_s]
138
+ end
98
139
  end
99
140
 
100
- it 'has a filter with the right name' do
101
- expect(filter.filters[:object].name).to eql(:object)
141
+ context 'with another option set' do
142
+ let(:block) { proc { public_send(:object, converter: :new) } }
143
+ let(:name) { :objects }
144
+
145
+ it 'has a filter with the right options' do
146
+ expect(filter.filters[:'0'].options).to have_key(:class)
147
+ expect(filter.filters[:'0'].options[:class]).to eql :Object
148
+ expect(filter.filters[:'0'].options).to have_key(:converter)
149
+ expect(filter.filters[:'0'].options[:converter]).to eql :new
150
+ end
102
151
  end
103
152
  end
104
153
  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
@@ -1,5 +1,3 @@
1
- # coding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ActiveInteraction::DateFilter, :filter do
@@ -15,7 +13,7 @@ describe ActiveInteraction::DateFilter, :filter do
15
13
  end
16
14
 
17
15
  describe '#cast' do
18
- let(:result) { filter.cast(value, nil) }
16
+ let(:result) { filter.send(:cast, value, nil) }
19
17
 
20
18
  context 'with a Date' do
21
19
  let(:value) { Date.new }
@@ -63,6 +61,48 @@ describe ActiveInteraction::DateFilter, :filter do
63
61
  end
64
62
  end
65
63
 
64
+ context 'with an implicit String' do
65
+ let(:value) do
66
+ Class.new do
67
+ def to_str
68
+ '2011-12-13'
69
+ end
70
+ end.new
71
+ end
72
+
73
+ it 'returns a Date' do
74
+ expect(result).to eql Date.parse(value)
75
+ end
76
+ end
77
+
78
+ context 'with a blank String' do
79
+ let(:value) do
80
+ Class.new do
81
+ def to_str
82
+ ' '
83
+ end
84
+ end.new
85
+ end
86
+
87
+ context 'optional' do
88
+ include_context 'optional'
89
+
90
+ it 'returns the default' do
91
+ expect(result).to eql options[:default]
92
+ end
93
+ end
94
+
95
+ context 'required' do
96
+ include_context 'required'
97
+
98
+ it 'raises an error' do
99
+ expect do
100
+ result
101
+ end.to raise_error ActiveInteraction::MissingValueError
102
+ end
103
+ end
104
+ end
105
+
66
106
  context 'with a GroupedInput' do
67
107
  let(:year) { 2012 }
68
108
  let(:month) { 1 }