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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -3
- data/README.md +8 -6
- data/lib/active_interaction.rb +5 -3
- data/lib/active_interaction/active_model.rb +29 -0
- data/lib/active_interaction/base.rb +82 -116
- data/lib/active_interaction/errors.rb +79 -5
- data/lib/active_interaction/filter.rb +195 -21
- data/lib/active_interaction/filters.rb +26 -0
- data/lib/active_interaction/filters/array_filter.rb +22 -25
- data/lib/active_interaction/filters/boolean_filter.rb +12 -12
- data/lib/active_interaction/filters/date_filter.rb +32 -5
- data/lib/active_interaction/filters/date_time_filter.rb +34 -7
- data/lib/active_interaction/filters/file_filter.rb +12 -9
- data/lib/active_interaction/filters/float_filter.rb +13 -11
- data/lib/active_interaction/filters/hash_filter.rb +36 -17
- data/lib/active_interaction/filters/integer_filter.rb +13 -11
- data/lib/active_interaction/filters/model_filter.rb +15 -15
- data/lib/active_interaction/filters/string_filter.rb +19 -8
- data/lib/active_interaction/filters/symbol_filter.rb +29 -0
- data/lib/active_interaction/filters/time_filter.rb +38 -16
- data/lib/active_interaction/method_missing.rb +18 -0
- data/lib/active_interaction/overload_hash.rb +1 -0
- data/lib/active_interaction/validation.rb +19 -0
- data/lib/active_interaction/version.rb +1 -1
- data/spec/active_interaction/active_model_spec.rb +33 -0
- data/spec/active_interaction/base_spec.rb +54 -48
- data/spec/active_interaction/errors_spec.rb +99 -0
- data/spec/active_interaction/filter_spec.rb +12 -20
- data/spec/active_interaction/filters/array_filter_spec.rb +50 -28
- data/spec/active_interaction/filters/boolean_filter_spec.rb +15 -15
- data/spec/active_interaction/filters/date_filter_spec.rb +30 -18
- data/spec/active_interaction/filters/date_time_filter_spec.rb +31 -19
- data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
- data/spec/active_interaction/filters/float_filter_spec.rb +13 -11
- data/spec/active_interaction/filters/hash_filter_spec.rb +38 -29
- data/spec/active_interaction/filters/integer_filter_spec.rb +18 -8
- data/spec/active_interaction/filters/model_filter_spec.rb +24 -20
- data/spec/active_interaction/filters/string_filter_spec.rb +14 -8
- data/spec/active_interaction/filters/symbol_filter_spec.rb +24 -0
- data/spec/active_interaction/filters/time_filter_spec.rb +33 -69
- data/spec/active_interaction/filters_spec.rb +21 -0
- data/spec/active_interaction/i18n_spec.rb +0 -15
- data/spec/active_interaction/integration/array_interaction_spec.rb +2 -22
- data/spec/active_interaction/integration/hash_interaction_spec.rb +5 -25
- data/spec/active_interaction/integration/symbol_interaction_spec.rb +5 -0
- data/spec/active_interaction/method_missing_spec.rb +69 -0
- data/spec/active_interaction/validation_spec.rb +55 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/filters.rb +168 -14
- data/spec/support/interactions.rb +11 -13
- metadata +31 -13
- data/lib/active_interaction/filter_method.rb +0 -13
- data/lib/active_interaction/filter_methods.rb +0 -26
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +0 -25
- data/spec/active_interaction/filter_method_spec.rb +0 -43
- 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
|
10
|
-
# @option options [String] :format
|
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 <
|
23
|
-
def
|
24
|
+
class TimeFilter < Filter
|
25
|
+
def cast(value)
|
24
26
|
case value
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
@@ -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
|
@@ -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
|
-
|
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 :
|
17
|
-
|
18
|
-
|
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 "
|
22
|
-
|
23
|
-
|
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(
|
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(
|
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(
|
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).
|
180
|
+
expect(outcome).to_not be_valid
|
176
181
|
end
|
177
182
|
|
178
183
|
it 'sets the result to nil' do
|
179
|
-
expect(
|
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,
|
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
|
-
|
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(
|
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(
|
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::
|
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 '#
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
-
|
276
|
-
|
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
|
-
|
282
|
-
|
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 '#
|
288
|
-
it '
|
289
|
-
expect
|
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
|