active_interaction 0.5.0 → 0.6.1

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 (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