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
@@ -6,41 +6,63 @@ module ActiveInteraction
6
6
  # in which case they will be processed with `strptime`. If `Time.zone` is
7
7
  # available it will be used so that the values are time zone aware.
8
8
  #
9
- # @macro attribute_method_params
10
- # @option options [String] :format Parse strings using this format string.
9
+ # @macro filter_method_params
10
+ # @option options [String] :format parse strings using this format string
11
11
  #
12
12
  # @example
13
13
  # time :start_date
14
14
  #
15
15
  # @example
16
- # date_time :start_date, format: '%Y-%m-%dT%H:%M:%S'
16
+ # date_time :start_date, format: '%Y-%m-%dT%H:%M:%S%z'
17
+ #
18
+ # @since 0.1.0
17
19
  #
18
20
  # @method self.time(*attributes, options = {})
19
21
  end
20
22
 
21
23
  # @private
22
- class TimeFilter < AbstractDateTimeFilter
23
- def self.prepare(key, value, options = {}, &block)
24
+ class TimeFilter < Filter
25
+ def cast(value)
24
26
  case value
25
- when Numeric
26
- time.at(value)
27
- else
28
- super(key, value, options.merge(class: time_class), &block)
27
+ when klass
28
+ value
29
+ when Numeric
30
+ time.at(value)
31
+ when String
32
+ begin
33
+ if has_format?
34
+ klass.strptime(value, format)
35
+ else
36
+ klass.parse(value)
37
+ end
38
+ rescue ArgumentError
39
+ super
40
+ end
41
+ else
42
+ super
29
43
  end
30
44
  end
31
45
 
32
- def self.time
46
+ private
47
+
48
+ def format
49
+ options.fetch(:format)
50
+ end
51
+
52
+ def has_format?
53
+ options.has_key?(:format)
54
+ end
55
+
56
+ def klass
57
+ time.at(0).class
58
+ end
59
+
60
+ def time
33
61
  if Time.respond_to?(:zone) && !Time.zone.nil?
34
62
  Time.zone
35
63
  else
36
64
  Time
37
65
  end
38
66
  end
39
- private_class_method :time
40
-
41
- def self.time_class
42
- time.at(0).class
43
- end
44
- private_class_method :time_class
45
67
  end
46
68
  end
@@ -0,0 +1,18 @@
1
+ module ActiveInteraction
2
+ # @private
3
+ module MethodMissing
4
+ def method_missing(slug, *args, &block)
5
+ begin
6
+ klass = Filter.factory(slug)
7
+ rescue MissingFilterError
8
+ super
9
+ end
10
+
11
+ options = args.last.is_a?(Hash) ? args.pop : {}
12
+
13
+ yield(klass, args, options) if block_given?
14
+
15
+ self
16
+ end
17
+ end
18
+ end
@@ -1,4 +1,5 @@
1
1
  module ActiveInteraction
2
+ # @private
2
3
  module OverloadHash
3
4
  def hash(*args, &block)
4
5
  if args.empty? && !block_given?
@@ -0,0 +1,19 @@
1
+ module ActiveInteraction
2
+ # @private
3
+ module Validation
4
+ def self.validate(filters, inputs)
5
+ filters.reduce([]) do |errors, filter|
6
+ begin
7
+ filter.cast(inputs[filter.name])
8
+
9
+ errors
10
+ rescue InvalidValueError
11
+ type = I18n.translate("#{Base.i18n_scope}.types.#{filter.class.slug}")
12
+ errors << [filter.name, :invalid, nil, type: type]
13
+ rescue MissingValueError
14
+ errors << [filter.name, :missing]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveInteraction
2
- VERSION = Gem::Version.new('0.5.0')
2
+ VERSION = Gem::Version.new('0.6.1')
3
3
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::ActiveModel do
4
+ subject(:model) do
5
+ Class.new do
6
+ include ActiveInteraction::ActiveModel
7
+ end
8
+ end
9
+
10
+ describe '#new_record?' do
11
+ it 'returns true' do
12
+ expect(model.new).to be_new_record
13
+ end
14
+ end
15
+
16
+ describe '#persisted?' do
17
+ it 'returns false' do
18
+ expect(model.new).to_not be_persisted
19
+ end
20
+ end
21
+
22
+ describe '.i18n_scope' do
23
+ it 'returns the scope' do
24
+ expect(model.i18n_scope).to eq :active_interaction
25
+ end
26
+ end
27
+
28
+ describe '#i18n_scope' do
29
+ it 'returns the scope' do
30
+ expect(model.new.i18n_scope).to eq :active_interaction
31
+ end
32
+ end
33
+ end
@@ -1,7 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ActiveInteraction::Base do
4
- let(:options) { {} }
4
+ include_context 'interactions'
5
+
5
6
  subject(:interaction) { described_class.new(options) }
6
7
 
7
8
  class InteractionWithFilter < described_class
@@ -13,27 +14,29 @@ describe ActiveInteraction::Base do
13
14
  end
14
15
 
15
16
  describe '.new(options = {})' do
16
- it 'does not allow :result as an option' do
17
- options.merge!(result: nil)
18
- expect { interaction }.to raise_error ArgumentError
17
+ it 'does not allow :_interaction_* as an option' do
18
+ key = :"_interaction_#{SecureRandom.hex}"
19
+ options.merge!(key => nil)
20
+ expect {
21
+ interaction
22
+ }.to raise_error ActiveInteraction::InvalidValueError
19
23
  end
20
24
 
21
- it 'does not allow "result" as an option' do
22
- options.merge!('result' => nil)
23
- expect { interaction }.to raise_error ArgumentError
25
+ it 'does not allow "_interaction_*" as an option' do
26
+ key = "_interaction_#{SecureRandom.hex}"
27
+ options.merge!(key => nil)
28
+ expect {
29
+ interaction
30
+ }.to raise_error ActiveInteraction::InvalidValueError
24
31
  end
25
32
 
26
33
  context 'with an attribute' do
27
34
  let(:described_class) do
28
- Class.new(ActiveInteraction::Base) do
35
+ Class.new(TestInteraction) do
29
36
  attr_reader :thing
30
37
 
31
38
  validates :thing, presence: true
32
39
 
33
- def self.name
34
- SecureRandom.hex
35
- end
36
-
37
40
  def execute
38
41
  thing
39
42
  end
@@ -104,6 +107,14 @@ describe ActiveInteraction::Base do
104
107
  }.to raise_error NoMethodError
105
108
  end
106
109
 
110
+ it do
111
+ expect do
112
+ Class.new(described_class) do
113
+ float :_interaction_thing
114
+ end
115
+ end.to raise_error ActiveInteraction::InvalidFilterError
116
+ end
117
+
107
118
  context 'with a filter' do
108
119
  let(:described_class) { InteractionWithFilter }
109
120
 
@@ -142,41 +153,35 @@ describe ActiveInteraction::Base do
142
153
  let(:thing) { rand }
143
154
 
144
155
  describe '.run(options = {})' do
145
- subject(:outcome) { described_class.run(options) }
146
-
147
156
  it "returns an instance of #{described_class}" do
148
157
  expect(outcome).to be_a described_class
149
158
  end
150
159
 
151
160
  context 'setting the result' do
152
161
  let(:described_class) do
153
- Class.new(ActiveInteraction::Base) do
162
+ Class.new(TestInteraction) do
154
163
  boolean :attribute
155
164
 
156
165
  validate do
157
166
  @_interaction_result = SecureRandom.hex
158
167
  errors.add(:attribute, SecureRandom.hex)
159
168
  end
160
-
161
- def self.name
162
- SecureRandom.hex
163
- end
164
169
  end
165
170
  end
166
171
 
167
172
  it 'sets the result to nil' do
168
173
  expect(outcome).to be_invalid
169
- expect(outcome.result).to be_nil
174
+ expect(result).to be_nil
170
175
  end
171
176
  end
172
177
 
173
178
  context 'failing validations' do
174
179
  it 'returns an invalid outcome' do
175
- expect(outcome).to be_invalid
180
+ expect(outcome).to_not be_valid
176
181
  end
177
182
 
178
183
  it 'sets the result to nil' do
179
- expect(outcome.result).to be_nil
184
+ expect(result).to be_nil
180
185
  end
181
186
  end
182
187
 
@@ -187,12 +192,15 @@ describe ActiveInteraction::Base do
187
192
  before do
188
193
  @execute = described_class.instance_method(:execute)
189
194
  described_class.send(:define_method, :execute) do
190
- errors.add(:thing, SecureRandom.hex)
195
+ errors.add(:thing, 'error')
196
+ errors.add_sym(:thing, :error, 'error')
191
197
  end
192
198
  end
193
199
 
194
200
  after do
195
- described_class.send(:define_method, :execute, @execute)
201
+ silence_warnings do
202
+ described_class.send(:define_method, :execute, @execute)
203
+ end
196
204
  end
197
205
 
198
206
  it 'returns an invalid outcome' do
@@ -200,7 +208,15 @@ describe ActiveInteraction::Base do
200
208
  end
201
209
 
202
210
  it 'sets the result to nil' do
203
- expect(outcome.result).to be_nil
211
+ expect(result).to be_nil
212
+ end
213
+
214
+ it 'has errors' do
215
+ expect(outcome.errors.messages[:thing]).to eq %w(error error)
216
+ end
217
+
218
+ it 'has symbolic errors' do
219
+ expect(outcome.errors.symbolic[:thing]).to eq [:error]
204
220
  end
205
221
  end
206
222
 
@@ -209,7 +225,7 @@ describe ActiveInteraction::Base do
209
225
  end
210
226
 
211
227
  it 'sets the result' do
212
- expect(outcome.result).to eq thing
228
+ expect(result).to eq thing
213
229
  end
214
230
 
215
231
  it 'calls transaction' do
@@ -246,7 +262,7 @@ describe ActiveInteraction::Base do
246
262
  it 'raises an error' do
247
263
  expect {
248
264
  result
249
- }.to raise_error ActiveInteraction::InteractionInvalid
265
+ }.to raise_error ActiveInteraction::InteractionInvalidError
250
266
  end
251
267
  end
252
268
 
@@ -260,33 +276,23 @@ describe ActiveInteraction::Base do
260
276
  end
261
277
  end
262
278
 
263
- describe '#execute' do
264
- it 'raises an error' do
265
- expect { interaction.execute }.to raise_error NotImplementedError
266
- end
267
- end
268
-
269
- describe '#new_record?' do
270
- it 'returns true' do
271
- expect(interaction).to be_new_record
272
- end
273
- end
279
+ describe '#inputs' do
280
+ let(:described_class) { InteractionWithFilter }
281
+ let(:other_val) { SecureRandom.hex }
282
+ let(:options) { {thing: 1, other: other_val} }
274
283
 
275
- describe '#persisted?' do
276
- it 'returns false' do
277
- expect(interaction).to_not be_persisted
284
+ it 'casts filtered inputs' do
285
+ expect(interaction.inputs[:thing]).to eql 1.0
278
286
  end
279
- end
280
287
 
281
- describe '.i18n_scope' do
282
- it 'returns the scope' do
283
- expect(described_class.i18n_scope).to eq :active_interaction
288
+ it 'strips non-filtered inputs' do
289
+ expect(interaction.inputs).to_not have_key(:other)
284
290
  end
285
291
  end
286
292
 
287
- describe '#i18n_scope' do
288
- it 'returns the scope' do
289
- expect(interaction.i18n_scope).to eq :active_interaction
293
+ describe '#execute' do
294
+ it 'raises an error' do
295
+ expect { interaction.execute }.to raise_error NotImplementedError
290
296
  end
291
297
  end
292
298
  end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::Errors do
4
+ let(:klass) do
5
+ Class.new do
6
+ include ActiveModel::Model
7
+
8
+ attr_reader :attribute
9
+
10
+ def self.name
11
+ SecureRandom.hex
12
+ end
13
+ end
14
+ end
15
+
16
+ subject(:errors) { described_class.new(klass.new) }
17
+
18
+ describe '#add_sym' do
19
+ it 'defaults to :invalid' do
20
+ errors.add_sym(:attribute)
21
+ expect(errors.symbolic).to eq({ attribute: [:invalid] })
22
+ end
23
+
24
+ it 'adds a symbol' do
25
+ errors.add_sym(:attribute, :symbol)
26
+ expect(errors.symbolic).to eq({ attribute: [:symbol] })
27
+ end
28
+
29
+ it 'accepts a message' do
30
+ errors.add_sym(:attribute, :symbol, 'message')
31
+ expect(errors.symbolic).to eq({ attribute: [:symbol] })
32
+ end
33
+
34
+ it 'accepts a message and options' do
35
+ errors.add_sym(:attribute, :symbol, 'message', { key: :value })
36
+ expect(errors.symbolic).to eq({ attribute: [:symbol] })
37
+ end
38
+
39
+ context 'calling #add' do
40
+ before do
41
+ allow(errors).to receive(:add)
42
+ end
43
+
44
+ it 'with the default' do
45
+ errors.add_sym(:attribute)
46
+ expect(errors).to have_received(:add).once.
47
+ with(:attribute, :invalid, {})
48
+ end
49
+
50
+ it 'with a symbol' do
51
+ errors.add_sym(:attribute, :symbol)
52
+ expect(errors).to have_received(:add).once.
53
+ with(:attribute, :symbol, {})
54
+ end
55
+
56
+ it 'with a symbol and message' do
57
+ errors.add_sym(:attribute, :symbol, 'message')
58
+ expect(errors).to have_received(:add).once.
59
+ with(:attribute, 'message', {})
60
+ end
61
+
62
+ it 'with a symbol, message and options' do
63
+ errors.add_sym(:attribute, :symbol, 'message', { key: :value })
64
+ expect(errors).to have_received(:add).once.
65
+ with(:attribute, 'message', { key: :value })
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#initialize' do
71
+ it 'sets symbolic to an empty hash' do
72
+ expect(errors.symbolic).to eq({})
73
+ end
74
+ end
75
+
76
+ describe '#initialize_dup' do
77
+ let(:errors_dup) { errors.dup }
78
+
79
+ before do
80
+ errors.add_sym(:attribute)
81
+ end
82
+
83
+ it 'dups symbolic' do
84
+ expect(errors_dup.symbolic).to eq errors.symbolic
85
+ expect(errors_dup.symbolic).to_not equal errors.symbolic
86
+ end
87
+ end
88
+
89
+ describe '#clear' do
90
+ before do
91
+ errors.add_sym(:attribute)
92
+ end
93
+
94
+ it 'clears symbolic' do
95
+ errors.clear
96
+ expect(errors.symbolic).to be_empty
97
+ end
98
+ end
99
+ end