active_interaction 3.5.3 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fbdbdde0bcea450c62432ce3c008b01b8f848353
4
- data.tar.gz: 99703c006de4b26a29258967e8bd0bd710b98670
3
+ metadata.gz: f61c20e8e7ee4f4f1d84d6244f11e4efae82a8b6
4
+ data.tar.gz: 5164aa102d888aa3405cf66be6b7a72a809fac83
5
5
  SHA512:
6
- metadata.gz: f1c89ec2d431e698c8a4e7917c4f17a46a91b6275a6efa686078437752318f8c2c0bf555dd270c90c862c05e0f60e2912edf8f3cd3f81cd98804c62f5de56977
7
- data.tar.gz: ae99d37c104721aac9920dc3dc4e9deec5507dbbdb6fcddef4c10222371f46d6ad95957f328372d2dd196356d93d03af755b75f444cdcd429f66bb84a0231203
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.5'
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.5'
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][] from [OrgSync][]. We were inspired by the fantastic work done
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
@@ -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
- def cast(value, context, reconstantize = true)
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
- else
30
- return super(value, context) unless reconstantize
31
-
38
+ elsif reconstantize
32
39
  @klass = klass
33
- cast(value, context, false)
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
@@ -18,6 +18,7 @@ en:
18
18
  integer: integer
19
19
  interface: interface
20
20
  object: object
21
+ record: record
21
22
  string: string
22
23
  symbol: symbol
23
24
  time: time
@@ -18,6 +18,7 @@ fr:
18
18
  integer: nombre entier
19
19
  interface: interface
20
20
  object: objet
21
+ record: record
21
22
  string: chaîne de caractères
22
23
  symbol: symbole
23
24
  time: temps
@@ -18,6 +18,7 @@ pt-BR:
18
18
  integer: integer
19
19
  interface: interface
20
20
  object: object
21
+ record: record
21
22
  string: string
22
23
  symbol: symbol
23
24
  time: time
@@ -6,5 +6,5 @@ module ActiveInteraction
6
6
  # The version number.
7
7
  #
8
8
  # @return [Gem::Version]
9
- VERSION = Gem::Version.new('3.5.3')
9
+ VERSION = Gem::Version.new('3.6.0')
10
10
  end
@@ -108,7 +108,8 @@ describe ActiveInteraction::Errors do
108
108
  }
109
109
  }
110
110
  }
111
- })
111
+ }
112
+ )
112
113
 
113
114
  other.add(:attribute, :invalid_type, type: nil)
114
115
  end
@@ -2,19 +2,28 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- class Thing; end
6
- class Things; end
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] = Thing
22
+ options[:class] = ObjectThing
14
23
  end
15
24
 
16
25
  describe '#cast' do
17
- let(:value) { Thing.new }
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, :Thing)
29
- class Thing; end
30
- value = Thing.new
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, :Thing)
39
- class Thing; end
40
- class SubThing < Thing; end
41
- value = SubThing.new
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, :Thing) }
60
- after { class Thing; end }
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(Thing).to receive(:===).and_return(case_equality)
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] = Thing.superclass
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] = Thing.name
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) { Things.new }
120
+ let(:value) { ObjectThings.new }
124
121
 
125
- before { options[:class] = Things }
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
@@ -101,7 +101,8 @@ describe I18nInteraction do
101
101
  }
102
102
  },
103
103
  types: TYPES.each_with_object({}) { |e, a| a[e] = e.reverse }
104
- })
104
+ }
105
+ )
105
106
  end
106
107
  end
107
108
  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.5.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-09-28 00:00:00.000000000 Z
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: http://devblog.orgsync.com/active_interaction/
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