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