active_interaction 0.5.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -3
  3. data/README.md +8 -6
  4. data/lib/active_interaction.rb +5 -3
  5. data/lib/active_interaction/active_model.rb +29 -0
  6. data/lib/active_interaction/base.rb +82 -116
  7. data/lib/active_interaction/errors.rb +79 -5
  8. data/lib/active_interaction/filter.rb +195 -21
  9. data/lib/active_interaction/filters.rb +26 -0
  10. data/lib/active_interaction/filters/array_filter.rb +22 -25
  11. data/lib/active_interaction/filters/boolean_filter.rb +12 -12
  12. data/lib/active_interaction/filters/date_filter.rb +32 -5
  13. data/lib/active_interaction/filters/date_time_filter.rb +34 -7
  14. data/lib/active_interaction/filters/file_filter.rb +12 -9
  15. data/lib/active_interaction/filters/float_filter.rb +13 -11
  16. data/lib/active_interaction/filters/hash_filter.rb +36 -17
  17. data/lib/active_interaction/filters/integer_filter.rb +13 -11
  18. data/lib/active_interaction/filters/model_filter.rb +15 -15
  19. data/lib/active_interaction/filters/string_filter.rb +19 -8
  20. data/lib/active_interaction/filters/symbol_filter.rb +29 -0
  21. data/lib/active_interaction/filters/time_filter.rb +38 -16
  22. data/lib/active_interaction/method_missing.rb +18 -0
  23. data/lib/active_interaction/overload_hash.rb +1 -0
  24. data/lib/active_interaction/validation.rb +19 -0
  25. data/lib/active_interaction/version.rb +1 -1
  26. data/spec/active_interaction/active_model_spec.rb +33 -0
  27. data/spec/active_interaction/base_spec.rb +54 -48
  28. data/spec/active_interaction/errors_spec.rb +99 -0
  29. data/spec/active_interaction/filter_spec.rb +12 -20
  30. data/spec/active_interaction/filters/array_filter_spec.rb +50 -28
  31. data/spec/active_interaction/filters/boolean_filter_spec.rb +15 -15
  32. data/spec/active_interaction/filters/date_filter_spec.rb +30 -18
  33. data/spec/active_interaction/filters/date_time_filter_spec.rb +31 -19
  34. data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
  35. data/spec/active_interaction/filters/float_filter_spec.rb +13 -11
  36. data/spec/active_interaction/filters/hash_filter_spec.rb +38 -29
  37. data/spec/active_interaction/filters/integer_filter_spec.rb +18 -8
  38. data/spec/active_interaction/filters/model_filter_spec.rb +24 -20
  39. data/spec/active_interaction/filters/string_filter_spec.rb +14 -8
  40. data/spec/active_interaction/filters/symbol_filter_spec.rb +24 -0
  41. data/spec/active_interaction/filters/time_filter_spec.rb +33 -69
  42. data/spec/active_interaction/filters_spec.rb +21 -0
  43. data/spec/active_interaction/i18n_spec.rb +0 -15
  44. data/spec/active_interaction/integration/array_interaction_spec.rb +2 -22
  45. data/spec/active_interaction/integration/hash_interaction_spec.rb +5 -25
  46. data/spec/active_interaction/integration/symbol_interaction_spec.rb +5 -0
  47. data/spec/active_interaction/method_missing_spec.rb +69 -0
  48. data/spec/active_interaction/validation_spec.rb +55 -0
  49. data/spec/spec_helper.rb +6 -0
  50. data/spec/support/filters.rb +168 -14
  51. data/spec/support/interactions.rb +11 -13
  52. metadata +31 -13
  53. data/lib/active_interaction/filter_method.rb +0 -13
  54. data/lib/active_interaction/filter_methods.rb +0 -26
  55. data/lib/active_interaction/filters/abstract_date_time_filter.rb +0 -25
  56. data/spec/active_interaction/filter_method_spec.rb +0 -43
  57. data/spec/active_interaction/filter_methods_spec.rb +0 -30
@@ -37,7 +37,7 @@ describe ArrayInteraction do
37
37
  Class.new(ActiveInteraction::Base) do
38
38
  array :a, default: Object.new
39
39
  end
40
- }.to raise_error ActiveInteraction::InvalidDefaultValue
40
+ }.to raise_error ActiveInteraction::InvalidDefaultError
41
41
  end
42
42
  end
43
43
 
@@ -49,27 +49,7 @@ describe ArrayInteraction do
49
49
  array
50
50
  end
51
51
  end
52
- }.to raise_error ActiveInteraction::InvalidDefaultValue
53
- end
54
- end
55
-
56
- context 'with a validly nested default' do
57
- let(:described_class) do
58
- Class.new(ActiveInteraction::Base) do
59
- array :a do
60
- array default: [rand]
61
- end
62
- def execute; a end
63
- end
64
- end
65
- let(:options) { { a: [] } }
66
-
67
- it 'does not raise an error' do
68
- expect { described_class.run(options) }.to_not raise_error
69
- end
70
-
71
- it 'ignores the nested default value' do
72
- expect(described_class.run!(options)).to eq options[:a]
52
+ }.to raise_error ActiveInteraction::InvalidDefaultError
73
53
  end
74
54
  end
75
55
  end
@@ -4,8 +4,8 @@ class HashInteraction < ActiveInteraction::Base
4
4
  hash :a do
5
5
  hash :x
6
6
  end
7
- hash :b, default: { x: {} } do
8
- hash :x
7
+ hash :b, default: {} do
8
+ hash :x, default: {}
9
9
  end
10
10
 
11
11
  def execute
@@ -23,7 +23,7 @@ describe HashInteraction do
23
23
  before { options.merge!(a: a) }
24
24
 
25
25
  it 'returns the correct value for :a' do
26
- expect(result[:a]).to eq a
26
+ expect(result[:a]).to eq a.symbolize_keys
27
27
  end
28
28
 
29
29
  it 'returns the correct value for :b' do
@@ -37,7 +37,7 @@ describe HashInteraction do
37
37
  Class.new(ActiveInteraction::Base) do
38
38
  hash :a, default: Object.new
39
39
  end
40
- }.to raise_error ActiveInteraction::InvalidDefaultValue
40
+ }.to raise_error ActiveInteraction::InvalidDefaultError
41
41
  end
42
42
  end
43
43
 
@@ -49,27 +49,7 @@ describe HashInteraction do
49
49
  hash :x
50
50
  end
51
51
  end
52
- }.to raise_error ActiveInteraction::InvalidDefaultValue
53
- end
54
- end
55
-
56
- context 'with a validly nested default' do
57
- let(:described_class) do
58
- Class.new(ActiveInteraction::Base) do
59
- hash :a do
60
- hash :x, default: { y: rand }
61
- end
62
- def execute; a end
63
- end
64
- end
65
- let(:options) { { a: { x: {} } } }
66
-
67
- it 'does not raise an error' do
68
- expect { described_class.run(options) }.to_not raise_error
69
- end
70
-
71
- it 'merges the nested default value' do
72
- expect(described_class.run!(options)[:x]).to have_key(:y)
52
+ }.to raise_error ActiveInteraction::InvalidDefaultError
73
53
  end
74
54
  end
75
55
  end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'SymbolInteraction' do
4
+ it_behaves_like 'an interaction', :symbol, -> { SecureRandom.hex.to_sym }
5
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::MethodMissing do
4
+ let(:model) do
5
+ Class.new do
6
+ include ActiveInteraction::MethodMissing
7
+ end
8
+ end
9
+
10
+ subject(:instance) { model.new }
11
+
12
+ describe '#method_missing' do
13
+ context 'with invalid slug' do
14
+ let(:slug) { :slug }
15
+
16
+ it 'calls super' do
17
+ expect {
18
+ instance.method_missing(slug)
19
+ }.to raise_error NoMethodError
20
+ end
21
+ end
22
+
23
+ context 'with valid slug' do
24
+ let(:filter) { ActiveInteraction::Filter.factory(slug) }
25
+ let(:slug) { :boolean }
26
+
27
+ it 'returns self' do
28
+ expect(instance.method_missing(slug)).to eq instance
29
+ end
30
+
31
+ it 'yields' do
32
+ expect { |b|
33
+ instance.method_missing(slug, &b)
34
+ }.to yield_with_args(filter, [], {})
35
+ end
36
+
37
+ context 'with names' do
38
+ let(:names) { [:a, :b, :c] }
39
+
40
+ it 'yields' do
41
+ expect { |b|
42
+ instance.method_missing(:boolean, *names, &b)
43
+ }.to yield_with_args(filter, names, {})
44
+ end
45
+ end
46
+
47
+ context 'with options' do
48
+ let(:options) { { a: nil, b: false, c: true } }
49
+
50
+ it 'yields' do
51
+ expect { |b|
52
+ instance.method_missing(:boolean, options, &b)
53
+ }.to yield_with_args(filter, [], options)
54
+ end
55
+ end
56
+
57
+ context 'with names & options' do
58
+ let(:names) { [:a, :b, :c] }
59
+ let(:options) { { a: nil, b: false, c: true } }
60
+
61
+ it 'yields' do
62
+ expect { |b|
63
+ instance.method_missing(:boolean, *names, options, &b)
64
+ }.to yield_with_args(filter, names, options)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::Validation do
4
+ describe '.validate(filters, inputs)' do
5
+ let(:inputs) { {} }
6
+ let(:filter) { ActiveInteraction::Filter.new(:name, {}) }
7
+ let(:filters) { ActiveInteraction::Filters.new.add(filter) }
8
+ let(:result) { described_class.validate(filters, inputs) }
9
+
10
+ context 'no filters are given' do
11
+ let(:filters) { ActiveInteraction::Filters.new }
12
+
13
+ it 'returns no errors' do
14
+ expect(result).to eq []
15
+ end
16
+ end
17
+
18
+ context 'filter.cast returns a value' do
19
+ let(:inputs) { {name: 1} }
20
+
21
+ before do
22
+ filter.stub(:cast).and_return(1)
23
+ end
24
+
25
+ it 'returns no errors' do
26
+ expect(result).to eq []
27
+ end
28
+ end
29
+
30
+ context 'filter throws' do
31
+ before do
32
+ filter.stub(:cast).and_raise(exception)
33
+ end
34
+
35
+ context 'InvalidValueError' do
36
+ let(:exception) { ActiveInteraction::InvalidValueError }
37
+ let(:filter) { ActiveInteraction::FloatFilter.new(:name, {}) }
38
+
39
+ it 'returns an :invalid_nested error' do
40
+ type = I18n.translate("#{ActiveInteraction::Base.i18n_scope}.types.#{filter.class.slug.to_s}")
41
+
42
+ expect(result).to eq [[filter.name, :invalid, nil, type: type]]
43
+ end
44
+ end
45
+
46
+ context 'MissingValueError' do
47
+ let(:exception) { ActiveInteraction::MissingValueError }
48
+
49
+ it 'returns an :invalid_nested error' do
50
+ expect(result).to eq [[filter.name, :missing]]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -4,3 +4,9 @@ Coveralls.wear!
4
4
  require 'active_interaction'
5
5
 
6
6
  Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
7
+
8
+ RSpec.configure do |config|
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+ config.run_all_when_everything_filtered = true
11
+ config.filter_run_including :focus
12
+ end
@@ -1,37 +1,191 @@
1
1
  shared_context 'filters' do
2
- let(:key) { SecureRandom.hex }
3
- let(:value) { nil }
2
+ let(:block) { nil }
3
+ let(:name) { SecureRandom.hex.to_sym }
4
4
  let(:options) { {} }
5
- let(:block) { Proc.new {} }
6
- subject(:result) { described_class.prepare(key, value, options, &block) }
5
+
6
+ subject(:filter) { described_class.new(name, options, &block) }
7
+
8
+ shared_context 'optional' do
9
+ before do
10
+ options.merge!(default: nil)
11
+ end
12
+ end
13
+
14
+ shared_context 'required' do
15
+ before do
16
+ options.delete(:default)
17
+ end
18
+ end
7
19
  end
8
20
 
9
21
  shared_examples_for 'a filter' do
10
22
  include_context 'filters'
11
23
 
12
- context '.prepare(key, value, options = {}, &block)' do
13
- context 'with nil' do
24
+ describe '.factory' do
25
+ context 'with an invalid slug' do
14
26
  it 'raises an error' do
15
- expect { result }.to raise_error ActiveInteraction::MissingValue
27
+ expect {
28
+ described_class.factory(:invalid)
29
+ }.to raise_error ActiveInteraction::MissingFilterError
30
+ end
31
+ end
32
+
33
+ context 'with a valid slug' do
34
+ it 'returns a Filter' do
35
+ expect(
36
+ described_class.factory(described_class.slug)
37
+ ).to eq described_class
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '.slug' do
43
+ it 'returns a symbol' do
44
+ expect(described_class.slug).to be_a Symbol
45
+ end
46
+ end
47
+
48
+ describe '#cast' do
49
+ let(:value) { nil }
50
+
51
+ context 'optional' do
52
+ include_context 'optional'
53
+
54
+ it 'returns nil' do
55
+ expect(filter.cast(value)).to be_nil
16
56
  end
17
57
  end
18
58
 
19
- context 'with anything else' do
20
- let(:value) { Object.new }
59
+ context 'required' do
60
+ include_context 'required'
21
61
 
22
62
  it 'raises an error' do
23
- expect { result }.to raise_error ActiveInteraction::InvalidValue
63
+ expect {
64
+ filter.cast(value)
65
+ }.to raise_error ActiveInteraction::MissingValueError
66
+ end
67
+
68
+ context 'with an invalid default' do
69
+ let(:value) { Object.new }
70
+
71
+ it 'raises an error' do
72
+ expect {
73
+ filter.cast(value)
74
+ }.to raise_error ActiveInteraction::InvalidValueError
75
+ end
24
76
  end
25
77
  end
78
+ end
79
+
80
+ describe '#clean' do
81
+ let(:value) { nil }
26
82
 
27
83
  context 'optional' do
28
- before { options.merge!(allow_nil: true) }
84
+ include_context 'optional'
85
+
86
+ it 'returns the default' do
87
+ expect(filter.clean(value)).to eq options[:default]
88
+ end
89
+ end
29
90
 
30
- context 'with nil' do
31
- it 'returns nil' do
32
- expect(result).to be_nil
91
+ context 'required' do
92
+ include_context 'required'
93
+
94
+ it 'raises an error' do
95
+ expect {
96
+ filter.clean(value)
97
+ }.to raise_error ActiveInteraction::MissingValueError
98
+ end
99
+
100
+ context 'with an invalid value' do
101
+ let(:value) { Object.new }
102
+
103
+ it 'raises an error' do
104
+ expect {
105
+ filter.clean(value)
106
+ }.to raise_error ActiveInteraction::InvalidValueError
33
107
  end
34
108
  end
35
109
  end
110
+
111
+ context 'with an invalid default' do
112
+ before do
113
+ options.merge!(default: Object.new)
114
+ end
115
+
116
+ it 'raises an error' do
117
+ expect {
118
+ filter.clean(value)
119
+ }.to raise_error ActiveInteraction::InvalidDefaultError
120
+ end
121
+ end
122
+ end
123
+
124
+ describe '#default' do
125
+ context 'optional' do
126
+ include_context 'optional'
127
+
128
+ it 'returns the default' do
129
+ expect(filter.default).to eq options[:default]
130
+ end
131
+ end
132
+
133
+ context 'required' do
134
+ include_context 'required'
135
+
136
+ it 'raises an error' do
137
+ expect {
138
+ filter.default
139
+ }.to raise_error ActiveInteraction::NoDefaultError
140
+ end
141
+ end
142
+
143
+ context 'with an invalid default' do
144
+ before do
145
+ options.merge!(default: Object.new)
146
+ end
147
+
148
+ it 'raises an error' do
149
+ expect {
150
+ filter.default
151
+ }.to raise_error ActiveInteraction::InvalidDefaultError
152
+ end
153
+ end
154
+ end
155
+
156
+ describe '#filters' do
157
+ it 'returns Filters' do
158
+ expect(filter.filters).to be_an ActiveInteraction::Filters
159
+ end
160
+ end
161
+
162
+ describe '#has_default?' do
163
+ context 'optional' do
164
+ include_context 'optional'
165
+
166
+ it 'returns true' do
167
+ expect(filter).to have_default
168
+ end
169
+ end
170
+
171
+ context 'required' do
172
+ include_context 'required'
173
+
174
+ it 'returns false' do
175
+ expect(filter).to_not have_default
176
+ end
177
+ end
178
+ end
179
+
180
+ describe '#name' do
181
+ it 'returns the name' do
182
+ expect(filter.name).to eq name
183
+ end
184
+ end
185
+
186
+ describe '#options' do
187
+ it 'returns the options' do
188
+ expect(filter.options).to eq options
189
+ end
36
190
  end
37
191
  end
@@ -1,3 +1,12 @@
1
+ class TestInteraction < ActiveInteraction::Base
2
+ def self.name
3
+ SecureRandom.hex
4
+ end
5
+
6
+ def execute
7
+ end
8
+ end
9
+
1
10
  shared_context 'interactions' do
2
11
  let(:options) { {} }
3
12
  let(:outcome) { described_class.run(options) }
@@ -8,25 +17,18 @@ shared_examples_for 'an interaction' do |type, generator, filter_options = {}|
8
17
  include_context 'interactions'
9
18
 
10
19
  let(:described_class) do
11
- Class.new(ActiveInteraction::Base) do
20
+ Class.new(TestInteraction) do
12
21
  send(type, :required, filter_options)
13
- send(type, :optional, filter_options.merge(allow_nil: true))
22
+ send(type, :optional, filter_options.merge(default: nil))
14
23
  send(type, :default, filter_options.merge(default: generator.call))
15
- send(type, :nil_default,
16
- filter_options.merge(allow_nil: true, default: nil))
17
24
  send(type, :defaults_1, :defaults_2,
18
25
  filter_options.merge(default: generator.call))
19
26
 
20
- def self.name
21
- SecureRandom.hex
22
- end
23
-
24
27
  def execute
25
28
  {
26
29
  required: required,
27
30
  optional: optional,
28
31
  default: default,
29
- nil_default: nil_default,
30
32
  defaults_1: defaults_1,
31
33
  defaults_2: defaults_2
32
34
  }
@@ -66,10 +68,6 @@ shared_examples_for 'an interaction' do |type, generator, filter_options = {}|
66
68
  expect(result[:default]).to_not be_nil
67
69
  end
68
70
 
69
- it 'returns nil for :nil_default' do
70
- expect(result[:nil_default]).to be_nil
71
- end
72
-
73
71
  it 'does not return nil for :defaults_1' do
74
72
  expect(result[:defaults_1]).to_not be_nil
75
73
  end