active_interaction 3.5.3 → 3.6.0
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/README.md +59 -6
- data/lib/active_interaction.rb +1 -0
- data/lib/active_interaction/errors.rb +5 -0
- data/lib/active_interaction/filters/object_filter.rb +45 -5
- data/lib/active_interaction/filters/record_filter.rb +88 -0
- data/lib/active_interaction/locale/en.yml +1 -0
- data/lib/active_interaction/locale/fr.yml +1 -0
- data/lib/active_interaction/locale/pt-BR.yml +1 -0
- data/lib/active_interaction/version.rb +1 -1
- data/spec/active_interaction/errors_spec.rb +2 -1
- data/spec/active_interaction/filters/object_filter_spec.rb +137 -30
- data/spec/active_interaction/filters/record_filter_spec.rb +210 -0
- data/spec/active_interaction/i18n_spec.rb +2 -1
- data/spec/support/interactions.rb +8 -4
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f61c20e8e7ee4f4f1d84d6244f11e4efae82a8b6
|
|
4
|
+
data.tar.gz: 5164aa102d888aa3405cf66be6b7a72a809fac83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36e0b36fed1473f0a3c4f3a5008d6a8bb40186923b20122570f8df517c82495d6cfd9fdceea6c5924dbb481b62461f19f9447152d1d1f6f8a499c9935f85312a
|
|
7
|
+
data.tar.gz: cd74144ec70e28fc1c23fc530edf2195e275333838fed2f22037bb838cf5aaf8323cfb80a984feb0bc23e4c11cbd62352a915c10fc0dc380ac82fcf74c2ba38c
|
data/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Read more on [the project page][] or check out [the full documentation][].
|
|
|
28
28
|
- [Hash](#hash)
|
|
29
29
|
- [Interface](#interface)
|
|
30
30
|
- [Object](#object)
|
|
31
|
+
- [Record](#record)
|
|
31
32
|
- [String](#string)
|
|
32
33
|
- [Symbol](#symbol)
|
|
33
34
|
- [Dates and times](#dates-and-times)
|
|
@@ -66,13 +67,13 @@ Read more on [the project page][] or check out [the full documentation][].
|
|
|
66
67
|
Add it to your Gemfile:
|
|
67
68
|
|
|
68
69
|
``` rb
|
|
69
|
-
gem 'active_interaction', '~> 3.
|
|
70
|
+
gem 'active_interaction', '~> 3.6'
|
|
70
71
|
```
|
|
71
72
|
|
|
72
73
|
Or install it manually:
|
|
73
74
|
|
|
74
75
|
``` sh
|
|
75
|
-
$ gem install active_interaction --version '~> 3.
|
|
76
|
+
$ gem install active_interaction --version '~> 3.6'
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
This project uses [Semantic Versioning][]. Check out [GitHub releases][] for a
|
|
@@ -418,6 +419,61 @@ object :dolly3,
|
|
|
418
419
|
class: :Sheep
|
|
419
420
|
```
|
|
420
421
|
|
|
422
|
+
If you have value objects or you would like to build one object from another,
|
|
423
|
+
you can use the `converter` option. It is only called if the value provided does
|
|
424
|
+
not pass `#is_a?` and `.===` for the object class. The `converter` option
|
|
425
|
+
accepts a symbol that specifies a class method on the object class or a proc.
|
|
426
|
+
Both will be passed the value and any errors thrown inside the converter will
|
|
427
|
+
cause the value to be considered invalid. Any returned value that is not the
|
|
428
|
+
correct class will also be treated as invalid. The value given to the `default`
|
|
429
|
+
option will also be converted.
|
|
430
|
+
|
|
431
|
+
``` rb
|
|
432
|
+
class ObjectInteraction < ActiveInteraction::Base
|
|
433
|
+
object :ip_address,
|
|
434
|
+
class: IPAddr,
|
|
435
|
+
converter: :new
|
|
436
|
+
|
|
437
|
+
def execute
|
|
438
|
+
ip_address
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
ObjectInteraction.run!(ip_address: '192.168.1.1')
|
|
443
|
+
# #<IPAddr: IPv4:192.168.1.1/255.255.255.255>
|
|
444
|
+
|
|
445
|
+
ObjectInteraction.run!(ip_address: 1)
|
|
446
|
+
# ActiveInteraction::InvalidInteractionError: Ip address is not a valid object
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Record
|
|
450
|
+
|
|
451
|
+
Record filters allow you to require an instance of a particular class or a value
|
|
452
|
+
that can be used to locate an instance of the object. It checks either `#is_a?`
|
|
453
|
+
on the instance or `.===` on the class. If the value does not match, it will
|
|
454
|
+
call `find` on the class of the record. This is particularly useful when working
|
|
455
|
+
with ActiveRecord objects. Like an object filter, the class is derived from the
|
|
456
|
+
name passed but can be specified with the `class` option. The value given to the
|
|
457
|
+
`default` option will also be found.
|
|
458
|
+
|
|
459
|
+
``` rb
|
|
460
|
+
class RecordInteraction < ActiveInteraction::Base
|
|
461
|
+
record :encoding
|
|
462
|
+
|
|
463
|
+
def execute
|
|
464
|
+
encoding
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
> RecordInteraction.run!(encoding: Encoding::US_ASCII)
|
|
469
|
+
=> #<Encoding:US-ASCII>
|
|
470
|
+
|
|
471
|
+
> RecordInteraction.run!(encoding: 'ascii')
|
|
472
|
+
=> #<Encoding:US-ASCII>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
A different method can be specified by providing a symbol to the `finder` option.
|
|
476
|
+
|
|
421
477
|
### String
|
|
422
478
|
|
|
423
479
|
String filters define inputs that only accept strings.
|
|
@@ -1389,8 +1445,7 @@ I18nInteraction.run(name: false).errors.messages[:name]
|
|
|
1389
1445
|
## Credits
|
|
1390
1446
|
|
|
1391
1447
|
ActiveInteraction is brought to you by [Aaron Lasseigne][] and
|
|
1392
|
-
[Taylor Fausak][]
|
|
1393
|
-
by [Jonathan Novak][] on [Mutations][].
|
|
1448
|
+
[Taylor Fausak][] and was originally built at [OrgSync][].
|
|
1394
1449
|
|
|
1395
1450
|
If you want to contribute to ActiveInteraction, please read
|
|
1396
1451
|
[our contribution guidelines][]. A [complete list of contributors][] is
|
|
@@ -1408,8 +1463,6 @@ ActiveInteraction is licensed under [the MIT License][].
|
|
|
1408
1463
|
[aaron lasseigne]: https://github.com/AaronLasseigne
|
|
1409
1464
|
[taylor fausak]: https://github.com/tfausak
|
|
1410
1465
|
[orgsync]: https://github.com/orgsync
|
|
1411
|
-
[jonathan novak]: https://github.com/cypriss
|
|
1412
|
-
[mutations]: https://github.com/cypriss/mutations
|
|
1413
1466
|
[our contribution guidelines]: CONTRIBUTING.md
|
|
1414
1467
|
[complete list of contributors]: https://github.com/orgsync/active_interaction/graphs/contributors
|
|
1415
1468
|
[the mit license]: LICENSE.md
|
data/lib/active_interaction.rb
CHANGED
|
@@ -42,6 +42,7 @@ require 'active_interaction/filters/float_filter'
|
|
|
42
42
|
require 'active_interaction/filters/hash_filter'
|
|
43
43
|
require 'active_interaction/filters/integer_filter'
|
|
44
44
|
require 'active_interaction/filters/object_filter'
|
|
45
|
+
require 'active_interaction/filters/record_filter'
|
|
45
46
|
require 'active_interaction/filters/string_filter'
|
|
46
47
|
require 'active_interaction/filters/symbol_filter'
|
|
47
48
|
require 'active_interaction/filters/time_filter'
|
|
@@ -13,6 +13,11 @@ module ActiveInteraction
|
|
|
13
13
|
# @return [Class]
|
|
14
14
|
InvalidClassError = Class.new(Error)
|
|
15
15
|
|
|
16
|
+
# Raised if a converter is invalid.
|
|
17
|
+
#
|
|
18
|
+
# @return [Class]
|
|
19
|
+
InvalidConverterError = Class.new(Error)
|
|
20
|
+
|
|
16
21
|
# Raised if a default value is invalid.
|
|
17
22
|
#
|
|
18
23
|
# @return [Class]
|
|
@@ -10,6 +10,14 @@ module ActiveInteraction
|
|
|
10
10
|
# @!macro filter_method_params
|
|
11
11
|
# @option options [Class, String, Symbol] :class (use the attribute name)
|
|
12
12
|
# Class name used to ensure the value.
|
|
13
|
+
# @option options [Proc, Symbol] :converter A symbol specifying the name
|
|
14
|
+
# of a class method of `:class` or a Proc that is called when a new
|
|
15
|
+
# value is assigned to the value object. The converter is passed the
|
|
16
|
+
# single value that is used in the assignment and is only called if the
|
|
17
|
+
# new value is not an instance of `:class`. The class method or proc
|
|
18
|
+
# are passed the value. Any error thrown inside the converter is trapped
|
|
19
|
+
# and the value provided is treated as invalid. Any returned value that
|
|
20
|
+
# is not the correct class will also be treated as invalid.
|
|
13
21
|
#
|
|
14
22
|
# @example
|
|
15
23
|
# object :account
|
|
@@ -21,18 +29,29 @@ module ActiveInteraction
|
|
|
21
29
|
class ObjectFilter < Filter
|
|
22
30
|
register :object
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
# rubocop:disable Metrics/MethodLength
|
|
33
|
+
def cast(value, context, reconstantize: true, convert: true)
|
|
25
34
|
@klass ||= klass
|
|
26
35
|
|
|
27
36
|
if matches?(value)
|
|
28
37
|
value
|
|
29
|
-
|
|
30
|
-
return super(value, context) unless reconstantize
|
|
31
|
-
|
|
38
|
+
elsif reconstantize
|
|
32
39
|
@klass = klass
|
|
33
|
-
|
|
40
|
+
public_send(__method__, value, context,
|
|
41
|
+
reconstantize: false,
|
|
42
|
+
convert: convert
|
|
43
|
+
)
|
|
44
|
+
elsif !value.nil? && convert && (converter = options[:converter])
|
|
45
|
+
value = convert(klass, value, converter)
|
|
46
|
+
public_send(__method__, value, context,
|
|
47
|
+
reconstantize: reconstantize,
|
|
48
|
+
convert: false
|
|
49
|
+
)
|
|
50
|
+
else
|
|
51
|
+
super(value, context)
|
|
34
52
|
end
|
|
35
53
|
end
|
|
54
|
+
# rubocop:enable Metrics/MethodLength
|
|
36
55
|
|
|
37
56
|
private
|
|
38
57
|
|
|
@@ -53,5 +72,26 @@ module ActiveInteraction
|
|
|
53
72
|
@klass === value || # rubocop:disable Style/CaseEquality
|
|
54
73
|
value.is_a?(@klass)
|
|
55
74
|
end
|
|
75
|
+
|
|
76
|
+
def convert(klass, value, converter) # rubocop:disable Metrics/MethodLength
|
|
77
|
+
result =
|
|
78
|
+
case converter
|
|
79
|
+
when Proc
|
|
80
|
+
converter.call(value)
|
|
81
|
+
when Symbol
|
|
82
|
+
klass.public_send(converter, value)
|
|
83
|
+
else
|
|
84
|
+
raise InvalidConverterError,
|
|
85
|
+
"#{converter.inspect} is not a valid converter"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
raise InvalidValueError if result.nil?
|
|
89
|
+
|
|
90
|
+
result
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
raise e if e.is_a?(InvalidConverterError)
|
|
93
|
+
|
|
94
|
+
raise InvalidValueError
|
|
95
|
+
end
|
|
56
96
|
end
|
|
57
97
|
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveInteraction
|
|
4
|
+
class Base
|
|
5
|
+
# @!method self.record(*attributes, options = {})
|
|
6
|
+
# Creates accessors for the attributes and ensures that values passed to
|
|
7
|
+
# the attributes are the correct class.
|
|
8
|
+
#
|
|
9
|
+
# @!macro filter_method_params
|
|
10
|
+
# @option options [Class, String, Symbol] :class (use the attribute name)
|
|
11
|
+
# Class name used to ensure the value.
|
|
12
|
+
# @option options [Symbol] :finder A symbol specifying the name of a
|
|
13
|
+
# class method of `:class` that is called when a new value is assigned
|
|
14
|
+
# to the object. The finder is passed the single value that is used in
|
|
15
|
+
# the assignment and is only called if the new value is not an instance
|
|
16
|
+
# of `:class`. The class method is passed the value. Any error thrown
|
|
17
|
+
# inside the finder is trapped and the value provided is treated as
|
|
18
|
+
# invalid. Any returned value that is not the correct class will also
|
|
19
|
+
# be treated as invalid.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# record :account
|
|
23
|
+
# @example
|
|
24
|
+
# record :account, class: User
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @private
|
|
28
|
+
class RecordFilter < Filter
|
|
29
|
+
register :record
|
|
30
|
+
|
|
31
|
+
# rubocop:disable Metrics/MethodLength
|
|
32
|
+
def cast(value, context, reconstantize: true, convert: true)
|
|
33
|
+
@klass ||= klass
|
|
34
|
+
|
|
35
|
+
if matches?(value)
|
|
36
|
+
value
|
|
37
|
+
elsif reconstantize
|
|
38
|
+
@klass = klass
|
|
39
|
+
public_send(__method__, value, context,
|
|
40
|
+
reconstantize: false,
|
|
41
|
+
convert: convert
|
|
42
|
+
)
|
|
43
|
+
elsif !value.nil? && convert
|
|
44
|
+
finder = options.fetch(:finder, :find)
|
|
45
|
+
value = find(klass, value, finder)
|
|
46
|
+
public_send(__method__, value, context,
|
|
47
|
+
reconstantize: reconstantize,
|
|
48
|
+
convert: false
|
|
49
|
+
)
|
|
50
|
+
else
|
|
51
|
+
super(value, context)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
# rubocop:enable Metrics/MethodLength
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# @return [Class]
|
|
59
|
+
#
|
|
60
|
+
# @raise [InvalidClassError]
|
|
61
|
+
def klass
|
|
62
|
+
klass_name = options.fetch(:class, name).to_s.camelize
|
|
63
|
+
Object.const_get(klass_name)
|
|
64
|
+
rescue NameError
|
|
65
|
+
raise InvalidClassError, "class #{klass_name.inspect} does not exist"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param value [Object]
|
|
69
|
+
#
|
|
70
|
+
# @return [Boolean]
|
|
71
|
+
def matches?(value)
|
|
72
|
+
@klass === value || # rubocop:disable Style/CaseEquality
|
|
73
|
+
value.is_a?(@klass)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def find(klass, value, finder)
|
|
77
|
+
result = klass.public_send(finder, value)
|
|
78
|
+
|
|
79
|
+
raise InvalidValueError if result.nil?
|
|
80
|
+
|
|
81
|
+
result
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
raise e if e.is_a?(InvalidConverterError)
|
|
84
|
+
|
|
85
|
+
raise InvalidValueError
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -2,19 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
-
class
|
|
6
|
-
|
|
5
|
+
class ObjectThing
|
|
6
|
+
def self.converter(_)
|
|
7
|
+
@converter ||= new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.converter_with_error(_)
|
|
11
|
+
raise 'error'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
class ObjectThings; end
|
|
15
|
+
BackupObjectThing = ObjectThing
|
|
7
16
|
|
|
8
17
|
describe ActiveInteraction::ObjectFilter, :filter do
|
|
9
18
|
include_context 'filters'
|
|
10
19
|
it_behaves_like 'a filter'
|
|
11
20
|
|
|
12
21
|
before do
|
|
13
|
-
options[:class] =
|
|
22
|
+
options[:class] = ObjectThing
|
|
14
23
|
end
|
|
15
24
|
|
|
16
25
|
describe '#cast' do
|
|
17
|
-
let(:value) {
|
|
26
|
+
let(:value) { ObjectThing.new }
|
|
18
27
|
let(:result) { filter.cast(value, nil) }
|
|
19
28
|
|
|
20
29
|
context 'with class as a Class' do
|
|
@@ -25,9 +34,9 @@ describe ActiveInteraction::ObjectFilter, :filter do
|
|
|
25
34
|
it 'handles reconstantizing' do
|
|
26
35
|
expect(result).to eql value
|
|
27
36
|
|
|
28
|
-
Object.send(:remove_const, :
|
|
29
|
-
|
|
30
|
-
value =
|
|
37
|
+
Object.send(:remove_const, :ObjectThing)
|
|
38
|
+
ObjectThing = BackupObjectThing
|
|
39
|
+
value = ObjectThing.new
|
|
31
40
|
|
|
32
41
|
expect(filter.cast(value, nil)).to eql value
|
|
33
42
|
end
|
|
@@ -35,29 +44,17 @@ describe ActiveInteraction::ObjectFilter, :filter do
|
|
|
35
44
|
it 'handles reconstantizing subclasses' do
|
|
36
45
|
filter
|
|
37
46
|
|
|
38
|
-
Object.send(:remove_const, :
|
|
39
|
-
|
|
40
|
-
class
|
|
41
|
-
value =
|
|
47
|
+
Object.send(:remove_const, :ObjectThing)
|
|
48
|
+
ObjectThing = BackupObjectThing
|
|
49
|
+
class SubObjectThing < ObjectThing; end
|
|
50
|
+
value = SubObjectThing.new
|
|
42
51
|
|
|
43
52
|
expect(filter.cast(value, nil)).to eql value
|
|
44
53
|
end
|
|
45
54
|
|
|
46
|
-
it 'does not overflow the stack' do
|
|
47
|
-
klass = Class.new do
|
|
48
|
-
def self.name
|
|
49
|
-
Thing.name
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
expect do
|
|
54
|
-
filter.cast(klass.new, nil)
|
|
55
|
-
end.to raise_error ActiveInteraction::InvalidValueError
|
|
56
|
-
end
|
|
57
|
-
|
|
58
55
|
context 'without the class available' do
|
|
59
|
-
before { Object.send(:remove_const, :
|
|
60
|
-
after {
|
|
56
|
+
before { Object.send(:remove_const, :ObjectThing) }
|
|
57
|
+
after { ObjectThing = BackupObjectThing }
|
|
61
58
|
|
|
62
59
|
it 'does not raise an error on initialization' do
|
|
63
60
|
expect { filter }.to_not raise_error
|
|
@@ -69,7 +66,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
|
|
|
69
66
|
let(:class_equality) { false }
|
|
70
67
|
|
|
71
68
|
before do
|
|
72
|
-
allow(
|
|
69
|
+
allow(ObjectThing).to receive(:===).and_return(case_equality)
|
|
73
70
|
allow(value).to receive(:is_a?).and_return(class_equality)
|
|
74
71
|
end
|
|
75
72
|
|
|
@@ -101,7 +98,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
|
|
|
101
98
|
|
|
102
99
|
context 'with class as a superclass' do
|
|
103
100
|
before do
|
|
104
|
-
options[:class] =
|
|
101
|
+
options[:class] = ObjectThing.superclass
|
|
105
102
|
end
|
|
106
103
|
|
|
107
104
|
it 'returns the instance' do
|
|
@@ -111,7 +108,7 @@ describe ActiveInteraction::ObjectFilter, :filter do
|
|
|
111
108
|
|
|
112
109
|
context 'with class as a String' do
|
|
113
110
|
before do
|
|
114
|
-
options[:class] =
|
|
111
|
+
options[:class] = ObjectThing.name
|
|
115
112
|
end
|
|
116
113
|
|
|
117
114
|
it 'returns the instance' do
|
|
@@ -120,9 +117,9 @@ describe ActiveInteraction::ObjectFilter, :filter do
|
|
|
120
117
|
end
|
|
121
118
|
|
|
122
119
|
context 'with a plural class' do
|
|
123
|
-
let(:value) {
|
|
120
|
+
let(:value) { ObjectThings.new }
|
|
124
121
|
|
|
125
|
-
before { options[:class] =
|
|
122
|
+
before { options[:class] = ObjectThings }
|
|
126
123
|
|
|
127
124
|
it 'returns the instance' do
|
|
128
125
|
expect(result).to eql value
|
|
@@ -140,6 +137,116 @@ describe ActiveInteraction::ObjectFilter, :filter do
|
|
|
140
137
|
end.to raise_error ActiveInteraction::InvalidClassError
|
|
141
138
|
end
|
|
142
139
|
end
|
|
140
|
+
|
|
141
|
+
context 'with a converter' do
|
|
142
|
+
let(:value) { '' }
|
|
143
|
+
|
|
144
|
+
context 'that is a symbol' do
|
|
145
|
+
before do
|
|
146
|
+
options[:converter] = :converter
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'calls the class method' do
|
|
150
|
+
expect(result).to eql ObjectThing.converter(value)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
context 'that is a proc' do
|
|
155
|
+
before do
|
|
156
|
+
options[:converter] = ->(x) { ObjectThing.converter(x) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'gets called' do
|
|
160
|
+
expect(result).to eql ObjectThing.converter(value)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'with a object of the correct class' do
|
|
165
|
+
let(:value) { ObjectThing.new }
|
|
166
|
+
|
|
167
|
+
it 'does not call the converter' do
|
|
168
|
+
expect(ObjectThing).to_not receive(:converter)
|
|
169
|
+
expect(result).to eql value
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context 'with a object is a subclass' do
|
|
174
|
+
let(:subclass) { Class.new(ObjectThing) }
|
|
175
|
+
let(:value) { subclass.new }
|
|
176
|
+
|
|
177
|
+
it 'does not call the converter' do
|
|
178
|
+
expect(subclass).to_not receive(:converter)
|
|
179
|
+
expect(result).to eql value
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
context 'with a nil value' do
|
|
184
|
+
let(:value) { nil }
|
|
185
|
+
include_context 'optional'
|
|
186
|
+
|
|
187
|
+
it 'returns nil' do
|
|
188
|
+
expect(ObjectThing).to_not receive(:converter)
|
|
189
|
+
expect(result).to eql value
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context 'that is invalid' do
|
|
194
|
+
before do
|
|
195
|
+
options[:converter] = 'invalid converter'
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'raises an error' do
|
|
199
|
+
expect do
|
|
200
|
+
result
|
|
201
|
+
end.to raise_error ActiveInteraction::InvalidConverterError
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context 'that throws an error' do
|
|
206
|
+
before do
|
|
207
|
+
options[:converter] = :converter_with_error
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'raises an error' do
|
|
211
|
+
expect do
|
|
212
|
+
result
|
|
213
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
describe '#clean' do
|
|
220
|
+
context 'with a converter' do
|
|
221
|
+
context 'that returns a nil' do
|
|
222
|
+
let(:value) { '' }
|
|
223
|
+
|
|
224
|
+
before do
|
|
225
|
+
options[:default] = ObjectThing.new
|
|
226
|
+
options[:converter] = ->(_) { nil }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'raises an error' do
|
|
230
|
+
expect do
|
|
231
|
+
filter.clean(value, nil)
|
|
232
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context 'that returns an invalid value' do
|
|
237
|
+
let(:value) { '' }
|
|
238
|
+
|
|
239
|
+
before do
|
|
240
|
+
options[:converter] = ->(_) { 'invalid' }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it 'raises an error' do
|
|
244
|
+
expect do
|
|
245
|
+
filter.clean(value, nil)
|
|
246
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
143
250
|
end
|
|
144
251
|
|
|
145
252
|
describe '#database_column_type' do
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
class RecordThing
|
|
4
|
+
def self.find(_)
|
|
5
|
+
raise 'error'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.finder(_)
|
|
9
|
+
@record2 ||= new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.finds_nil(_)
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.finds_bad_value(_)
|
|
17
|
+
''
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.passthrough(x)
|
|
21
|
+
x
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
class RecordThings; end
|
|
25
|
+
BackupRecordThing = RecordThing
|
|
26
|
+
|
|
27
|
+
describe ActiveInteraction::RecordFilter, :filter do
|
|
28
|
+
include_context 'filters'
|
|
29
|
+
it_behaves_like 'a filter'
|
|
30
|
+
|
|
31
|
+
before do
|
|
32
|
+
options[:class] = RecordThing
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#cast' do
|
|
36
|
+
before do
|
|
37
|
+
options[:finder] = :finder
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
let(:value) { RecordThing.new }
|
|
41
|
+
let(:result) { filter.cast(value, nil) }
|
|
42
|
+
|
|
43
|
+
context 'with class as a Class' do
|
|
44
|
+
it 'returns the instance' do
|
|
45
|
+
expect(result).to eql value
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'handles reconstantizing' do
|
|
49
|
+
expect(result).to eql value
|
|
50
|
+
|
|
51
|
+
Object.send(:remove_const, :RecordThing)
|
|
52
|
+
RecordThing = BackupRecordThing
|
|
53
|
+
value = RecordThing.new
|
|
54
|
+
|
|
55
|
+
expect(filter.cast(value, nil)).to eql value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'handles reconstantizing subclasses' do
|
|
59
|
+
filter
|
|
60
|
+
|
|
61
|
+
Object.send(:remove_const, :RecordThing)
|
|
62
|
+
RecordThing = BackupRecordThing
|
|
63
|
+
class SubRecordThing < RecordThing; end
|
|
64
|
+
value = SubRecordThing.new
|
|
65
|
+
|
|
66
|
+
expect(filter.cast(value, nil)).to eql value
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'without the class available' do
|
|
70
|
+
before { Object.send(:remove_const, :RecordThing) }
|
|
71
|
+
after { RecordThing = BackupRecordThing }
|
|
72
|
+
|
|
73
|
+
it 'does not raise an error on initialization' do
|
|
74
|
+
expect { filter }.to_not raise_error
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'with bidirectional class comparisons' do
|
|
79
|
+
let(:case_equality) { false }
|
|
80
|
+
let(:class_equality) { false }
|
|
81
|
+
|
|
82
|
+
before do
|
|
83
|
+
options[:finder] = :passthrough
|
|
84
|
+
|
|
85
|
+
allow(RecordThing).to receive(:===).and_return(case_equality)
|
|
86
|
+
allow(value).to receive(:is_a?).and_return(class_equality)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context 'without case or class equality' do
|
|
90
|
+
it 'raises an error' do
|
|
91
|
+
expect do
|
|
92
|
+
result
|
|
93
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context 'with case equality' do
|
|
98
|
+
let(:case_equality) { true }
|
|
99
|
+
|
|
100
|
+
it 'returns the instance' do
|
|
101
|
+
expect(result).to eql value
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context 'with class equality' do
|
|
106
|
+
let(:class_equality) { true }
|
|
107
|
+
|
|
108
|
+
it 'returns the instance' do
|
|
109
|
+
expect(result).to eql value
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context 'with class as a superclass' do
|
|
116
|
+
before do
|
|
117
|
+
options[:class] = RecordThing.superclass
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'returns the instance' do
|
|
121
|
+
expect(result).to eql value
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context 'with class as a String' do
|
|
126
|
+
before do
|
|
127
|
+
options[:class] = RecordThing.name
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'returns the instance' do
|
|
131
|
+
expect(result).to eql value
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context 'with a plural class' do
|
|
136
|
+
let(:value) { RecordThings.new }
|
|
137
|
+
|
|
138
|
+
before { options[:class] = RecordThings }
|
|
139
|
+
|
|
140
|
+
it 'returns the instance' do
|
|
141
|
+
expect(result).to eql value
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context 'with class as an invalid String' do
|
|
146
|
+
before do
|
|
147
|
+
options[:class] = 'invalid'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'raises an error' do
|
|
151
|
+
expect do
|
|
152
|
+
result
|
|
153
|
+
end.to raise_error ActiveInteraction::InvalidClassError
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
context 'with a value that does not match the class' do
|
|
158
|
+
let(:value) { '' }
|
|
159
|
+
|
|
160
|
+
it 'calls the finder' do
|
|
161
|
+
expect(result).to eql RecordThing.finder(value)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'with a custom finder' do
|
|
165
|
+
it 'calls the custom finder' do
|
|
166
|
+
expect(result).to eql RecordThing.finder(value)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
describe '#clean' do
|
|
173
|
+
context 'with a value that does not match the class' do
|
|
174
|
+
context 'that returns a nil' do
|
|
175
|
+
let(:value) { '' }
|
|
176
|
+
|
|
177
|
+
before do
|
|
178
|
+
options[:default] = RecordThing.new
|
|
179
|
+
options[:finder] = :finds_nil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'raises an error' do
|
|
183
|
+
expect do
|
|
184
|
+
filter.clean(value, nil)
|
|
185
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
context 'that returns an invalid value' do
|
|
190
|
+
let(:value) { '' }
|
|
191
|
+
|
|
192
|
+
before do
|
|
193
|
+
options[:finder] = :finds_bad_value
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'raises an error' do
|
|
197
|
+
expect do
|
|
198
|
+
filter.clean(value, nil)
|
|
199
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe '#database_column_type' do
|
|
206
|
+
it 'returns :string' do
|
|
207
|
+
expect(filter.database_column_type).to eql :string
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -24,11 +24,14 @@ shared_examples_for 'an interaction' do |type, generator, filter_options = {}|
|
|
|
24
24
|
public_send(type, :required, filter_options)
|
|
25
25
|
public_send(type, :optional, filter_options.merge(default: nil))
|
|
26
26
|
public_send(type, :default,
|
|
27
|
-
filter_options.merge(default: generator.call)
|
|
27
|
+
filter_options.merge(default: generator.call)
|
|
28
|
+
)
|
|
28
29
|
public_send(type, :defaults_1, :defaults_2,
|
|
29
|
-
filter_options.merge(default: generator.call)
|
|
30
|
+
filter_options.merge(default: generator.call)
|
|
31
|
+
)
|
|
30
32
|
public_send(type, :defaults_3,
|
|
31
|
-
filter_options.merge(default: -> { required })
|
|
33
|
+
filter_options.merge(default: -> { required })
|
|
34
|
+
)
|
|
32
35
|
end
|
|
33
36
|
end
|
|
34
37
|
|
|
@@ -36,7 +39,8 @@ shared_examples_for 'an interaction' do |type, generator, filter_options = {}|
|
|
|
36
39
|
let(:described_class) do
|
|
37
40
|
Class.new(TestInteraction) do
|
|
38
41
|
public_send(type, :default,
|
|
39
|
-
filter_options.merge(default: -> { Object.new })
|
|
42
|
+
filter_options.merge(default: -> { Object.new })
|
|
43
|
+
)
|
|
40
44
|
end
|
|
41
45
|
end
|
|
42
46
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_interaction
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aaron Lasseigne
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2017-
|
|
12
|
+
date: 2017-10-20 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activemodel
|
|
@@ -196,6 +196,7 @@ files:
|
|
|
196
196
|
- lib/active_interaction/filters/integer_filter.rb
|
|
197
197
|
- lib/active_interaction/filters/interface_filter.rb
|
|
198
198
|
- lib/active_interaction/filters/object_filter.rb
|
|
199
|
+
- lib/active_interaction/filters/record_filter.rb
|
|
199
200
|
- lib/active_interaction/filters/string_filter.rb
|
|
200
201
|
- lib/active_interaction/filters/symbol_filter.rb
|
|
201
202
|
- lib/active_interaction/filters/time_filter.rb
|
|
@@ -229,6 +230,7 @@ files:
|
|
|
229
230
|
- spec/active_interaction/filters/integer_filter_spec.rb
|
|
230
231
|
- spec/active_interaction/filters/interface_filter_spec.rb
|
|
231
232
|
- spec/active_interaction/filters/object_filter_spec.rb
|
|
233
|
+
- spec/active_interaction/filters/record_filter_spec.rb
|
|
232
234
|
- spec/active_interaction/filters/string_filter_spec.rb
|
|
233
235
|
- spec/active_interaction/filters/symbol_filter_spec.rb
|
|
234
236
|
- spec/active_interaction/filters/time_filter_spec.rb
|
|
@@ -253,7 +255,7 @@ files:
|
|
|
253
255
|
- spec/support/concerns.rb
|
|
254
256
|
- spec/support/filters.rb
|
|
255
257
|
- spec/support/interactions.rb
|
|
256
|
-
homepage:
|
|
258
|
+
homepage: https://github.com/AaronLasseigne/active_interaction
|
|
257
259
|
licenses:
|
|
258
260
|
- MIT
|
|
259
261
|
metadata: {}
|
|
@@ -296,6 +298,7 @@ test_files:
|
|
|
296
298
|
- spec/active_interaction/filters/decimal_filter_spec.rb
|
|
297
299
|
- spec/active_interaction/filters/file_filter_spec.rb
|
|
298
300
|
- spec/active_interaction/filters/interface_filter_spec.rb
|
|
301
|
+
- spec/active_interaction/filters/record_filter_spec.rb
|
|
299
302
|
- spec/active_interaction/filters/date_time_filter_spec.rb
|
|
300
303
|
- spec/active_interaction/i18n_spec.rb
|
|
301
304
|
- spec/active_interaction/integration/boolean_interaction_spec.rb
|