active_interaction 4.1.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +128 -1
- data/README.md +63 -28
- data/lib/active_interaction/array_input.rb +77 -0
- data/lib/active_interaction/base.rb +14 -98
- data/lib/active_interaction/concerns/active_recordable.rb +3 -3
- data/lib/active_interaction/concerns/missable.rb +2 -2
- data/lib/active_interaction/errors.rb +6 -88
- data/lib/active_interaction/exceptions.rb +47 -0
- data/lib/active_interaction/filter/column.rb +59 -0
- data/lib/active_interaction/filter/error.rb +40 -0
- data/lib/active_interaction/filter.rb +44 -53
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +9 -6
- data/lib/active_interaction/filters/abstract_numeric_filter.rb +7 -3
- data/lib/active_interaction/filters/array_filter.rb +34 -6
- data/lib/active_interaction/filters/boolean_filter.rb +4 -3
- data/lib/active_interaction/filters/date_filter.rb +1 -1
- data/lib/active_interaction/filters/date_time_filter.rb +1 -1
- data/lib/active_interaction/filters/decimal_filter.rb +1 -1
- data/lib/active_interaction/filters/float_filter.rb +1 -1
- data/lib/active_interaction/filters/hash_filter.rb +23 -15
- data/lib/active_interaction/filters/integer_filter.rb +1 -1
- data/lib/active_interaction/filters/interface_filter.rb +12 -12
- data/lib/active_interaction/filters/object_filter.rb +9 -3
- data/lib/active_interaction/filters/record_filter.rb +21 -11
- data/lib/active_interaction/filters/string_filter.rb +1 -1
- data/lib/active_interaction/filters/symbol_filter.rb +1 -1
- data/lib/active_interaction/filters/time_filter.rb +4 -4
- data/lib/active_interaction/hash_input.rb +43 -0
- data/lib/active_interaction/input.rb +23 -0
- data/lib/active_interaction/inputs.rb +157 -46
- data/lib/active_interaction/locale/en.yml +0 -1
- data/lib/active_interaction/locale/fr.yml +0 -1
- data/lib/active_interaction/locale/it.yml +0 -1
- data/lib/active_interaction/locale/ja.yml +0 -1
- data/lib/active_interaction/locale/pt-BR.yml +0 -1
- data/lib/active_interaction/modules/validation.rb +6 -17
- data/lib/active_interaction/version.rb +1 -1
- data/lib/active_interaction.rb +43 -36
- data/spec/active_interaction/array_input_spec.rb +166 -0
- data/spec/active_interaction/base_spec.rb +15 -240
- data/spec/active_interaction/concerns/active_modelable_spec.rb +3 -3
- data/spec/active_interaction/concerns/active_recordable_spec.rb +7 -7
- data/spec/active_interaction/concerns/hashable_spec.rb +8 -8
- data/spec/active_interaction/concerns/missable_spec.rb +9 -9
- data/spec/active_interaction/concerns/runnable_spec.rb +34 -32
- data/spec/active_interaction/errors_spec.rb +60 -43
- data/spec/active_interaction/{filter_column_spec.rb → filter/column_spec.rb} +3 -10
- data/spec/active_interaction/filter_spec.rb +6 -6
- data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +2 -2
- data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +2 -2
- data/spec/active_interaction/filters/array_filter_spec.rb +99 -16
- data/spec/active_interaction/filters/boolean_filter_spec.rb +12 -11
- data/spec/active_interaction/filters/date_filter_spec.rb +32 -27
- data/spec/active_interaction/filters/date_time_filter_spec.rb +34 -29
- data/spec/active_interaction/filters/decimal_filter_spec.rb +20 -18
- data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
- data/spec/active_interaction/filters/float_filter_spec.rb +19 -17
- data/spec/active_interaction/filters/hash_filter_spec.rb +16 -18
- data/spec/active_interaction/filters/integer_filter_spec.rb +24 -22
- data/spec/active_interaction/filters/interface_filter_spec.rb +105 -82
- data/spec/active_interaction/filters/object_filter_spec.rb +52 -36
- data/spec/active_interaction/filters/record_filter_spec.rb +61 -39
- data/spec/active_interaction/filters/string_filter_spec.rb +7 -7
- data/spec/active_interaction/filters/symbol_filter_spec.rb +6 -6
- data/spec/active_interaction/filters/time_filter_spec.rb +57 -34
- data/spec/active_interaction/hash_input_spec.rb +58 -0
- data/spec/active_interaction/i18n_spec.rb +22 -17
- data/spec/active_interaction/inputs_spec.rb +167 -23
- data/spec/active_interaction/integration/array_interaction_spec.rb +3 -7
- data/spec/active_interaction/modules/validation_spec.rb +8 -31
- data/spec/spec_helper.rb +8 -0
- data/spec/support/concerns.rb +2 -2
- data/spec/support/filters.rb +27 -51
- data/spec/support/interactions.rb +4 -4
- metadata +40 -91
- data/lib/active_interaction/filter_column.rb +0 -57
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cfed756a5463384a07d694ba40f14067dce231d2618af2f16e6fd429e31b97f
|
4
|
+
data.tar.gz: eb3b185d234762dec1799fbaa6e8acf290a47e0940b24c3da529f11172e6fde5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2ce2d6ef28bc84e2cc614dd9e6dde57c14a92efcafe706e8cb532b1c0fa9c192db8548771b94198236f3ab3ec6baa9633c2625c3f8b73cbcc1291752078be6c
|
7
|
+
data.tar.gz: 3f5f3c15059c19c7fec77256a260fc67f98444ab5dba48048f2797a441a348f7deea31b7b558e0b1af334ffd8a6e1685756745ecebdc87ee658dafc42f8043c4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,128 @@
|
|
1
|
+
# [5.0.0][] (2022-06-24)
|
2
|
+
|
3
|
+
## Changed
|
4
|
+
|
5
|
+
- Drop support for JRuby.
|
6
|
+
- Drop support for Ruby 2.5 and 2.6, adding support for 3.1
|
7
|
+
- Drop support for Rails 5.0 and 5.1
|
8
|
+
- `ActiveInteraction::Inputs` no longer inherits from `Hash` though it still has most of the methods
|
9
|
+
provided by `Hash` (methods that write were removed).
|
10
|
+
- Removed `Filter#clean` (use `Filter#process` and call `#value` on the result)
|
11
|
+
- The `given?` method has been moved onto `inputs`. ([how to upgrade](#given))
|
12
|
+
- [#503][] - The record filter now treats blank strings value as `nil`. This was missed in the 4.0 update.
|
13
|
+
- The `type_check` callback has been renamed to `filter` to better match the reality of what it does.
|
14
|
+
([how to upgrade](#filter-callback))
|
15
|
+
- `ActiveIneraction::FilterColumn` is now `ActiveInteraction::Filter::Column`
|
16
|
+
- Errors on the array filter will now be indexed if the Rails config `index_nested_attribute_errors`
|
17
|
+
is `true` or the `:index_errors` option is set to `true`. The `:index_errors` option always overrides
|
18
|
+
the Rails config.
|
19
|
+
- Invalid nested errors (`:invalid_nested`) are gone. Instead the nested errors will appear as they would
|
20
|
+
in Rails if they were a `has_many` relationship being assigned attributes through a parent.
|
21
|
+
([how to upgrade](#nested-hash-errors))
|
22
|
+
|
23
|
+
## Added
|
24
|
+
|
25
|
+
- `Filter#process` which returns an `Input`.
|
26
|
+
|
27
|
+
## Fixed
|
28
|
+
|
29
|
+
- When passing an `ActiveRecord::Relation` in an array filter with no inner
|
30
|
+
filter, the value returned was an `ActiveRecord::Relation` instead of an
|
31
|
+
Array.
|
32
|
+
|
33
|
+
## Upgrading
|
34
|
+
|
35
|
+
### `given?`
|
36
|
+
|
37
|
+
The `given?` method can now be found on `inputs`. It works the same as before.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# 4.1
|
41
|
+
class Example < ActiveInteraction::Base
|
42
|
+
string :name, default: nil
|
43
|
+
|
44
|
+
def execute
|
45
|
+
given?(:name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# 5.0
|
50
|
+
class Example < ActiveInteraction::Base
|
51
|
+
string :name, default: nil
|
52
|
+
|
53
|
+
def execute
|
54
|
+
inputs.given?(:name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
### Filter Callback
|
60
|
+
|
61
|
+
You'll need to rename any `:type_check` callbacks to `:filter`.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# 4.1
|
65
|
+
set_callback :type_check, :before, -> { puts 'before type check' }
|
66
|
+
|
67
|
+
# 5.0
|
68
|
+
set_callback :filter, :before, -> { puts 'before type check' }
|
69
|
+
```
|
70
|
+
|
71
|
+
### Nested Hash Errors
|
72
|
+
|
73
|
+
Nested hash errors no longer add an error as through it happened on the hash.
|
74
|
+
They now use the error in its original form and attach the name of the hash to
|
75
|
+
the error. It is also not limited to returning one error.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class HashInteraction < ActiveInteraction::Base
|
79
|
+
hash :mailing_lists do
|
80
|
+
boolean :marketing
|
81
|
+
boolean :product_updates
|
82
|
+
end
|
83
|
+
|
84
|
+
def execute
|
85
|
+
# ...
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
> outcome = HashInteraction.run(mailing_lists: {})
|
90
|
+
|
91
|
+
# 4.1
|
92
|
+
> outcome.errors.details
|
93
|
+
# => {:mailing_lists=>[{:error=>:invalid_nested, :name=>"\"marketing\"", :value=>"nil"}]},
|
94
|
+
> outcome.errors.messages
|
95
|
+
# => {:mailing_lists=>["has an invalid nested value (\"marketing\" => nil)"]}
|
96
|
+
> outcome.errors.full_messages
|
97
|
+
# => ["Mailing lists has an invalid nested value (\"marketing\" => nil)"]
|
98
|
+
|
99
|
+
# 5.0
|
100
|
+
> outcome.errors.details
|
101
|
+
# => {:"mailing_lists.marketing"=>[{:error=>:missing}], :"mailing_lists.product_updates"=>[{:error=>:missing}]}
|
102
|
+
> outcome.errors.messages
|
103
|
+
# => {:"mailing_lists.marketing"=>["is required"], :"mailing_lists.product_updates"=>["is required"]}
|
104
|
+
> outcome.errors.full_messages
|
105
|
+
# => ["Mailing lists marketing is required", "Mailing lists product updates is required"]
|
106
|
+
```
|
107
|
+
|
108
|
+
I18n can handle these values the same as nested values in Rails:
|
109
|
+
|
110
|
+
```yml
|
111
|
+
en:
|
112
|
+
active_interaction:
|
113
|
+
attributes:
|
114
|
+
hash_interaction/mailing_lists:
|
115
|
+
marketing: 'Mailing list "Marketing"'
|
116
|
+
product_updates: 'Mailing list "Product Updates"'
|
117
|
+
```
|
118
|
+
|
119
|
+
Using the same example from above:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
> outcome.errors.full_messages
|
123
|
+
# => ["Mailing list \"Marketing\" is required", "Mailing list \"Product Updates\" is required"]
|
124
|
+
```
|
125
|
+
|
1
126
|
# [4.1.0][] (2021-12-30)
|
2
127
|
|
3
128
|
## Added
|
@@ -68,7 +193,7 @@
|
|
68
193
|
|
69
194
|
## Added
|
70
195
|
|
71
|
-
- Implicit coercion of types are now supported in filters (e.g. to_str
|
196
|
+
- Implicit coercion of types are now supported in filters (e.g. `to_str`, `to_int`,
|
72
197
|
etc).
|
73
198
|
- The `interface` and `record` filters, when used as an inner filter for an
|
74
199
|
`array`, will have their `from/class` option set to a singularized version of
|
@@ -976,6 +1101,7 @@ Example.run
|
|
976
1101
|
|
977
1102
|
- Initial release.
|
978
1103
|
|
1104
|
+
[5.0.0]: https://github.com/AaronLasseigne/active_interaction/compare/v4.1.0...v5.0.0
|
979
1105
|
[4.1.0]: https://github.com/AaronLasseigne/active_interaction/compare/v4.0.6...v4.1.0
|
980
1106
|
[4.0.6]: https://github.com/AaronLasseigne/active_interaction/compare/v4.0.5...v4.0.6
|
981
1107
|
[4.0.5]: https://github.com/AaronLasseigne/active_interaction/compare/v4.0.4...v4.0.5
|
@@ -1198,3 +1324,4 @@ Example.run
|
|
1198
1324
|
[#480]: https://github.com/AaronLasseigne/active_interaction/issues/480
|
1199
1325
|
[#515]: https://github.com/AaronLasseigne/active_interaction/issues/515
|
1200
1326
|
[#518]: https://github.com/AaronLasseigne/active_interaction/issues/518
|
1327
|
+
[#503]: https://github.com/AaronLasseigne/active_interaction/issues/503
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# [ActiveInteraction][]
|
2
2
|
|
3
3
|
ActiveInteraction manages application-specific business logic.
|
4
|
-
It's an implementation of
|
4
|
+
It's an implementation of service objects designed to blend seamlessly into Rails.
|
5
5
|
|
6
6
|
[![Version](https://img.shields.io/gem/v/active_interaction.svg?style=flat-square)](https://rubygems.org/gems/active_interaction)
|
7
7
|
[![Test](https://img.shields.io/github/workflow/status/AaronLasseigne/active_interaction/Test?label=Test&style=flat-square)](https://github.com/AaronLasseigne/active_interaction/actions?query=workflow%3ATest)
|
@@ -51,7 +51,7 @@ handles your verbs.
|
|
51
51
|
- [Descriptions](#descriptions)
|
52
52
|
- [Errors](#errors)
|
53
53
|
- [Forms](#forms)
|
54
|
-
- [
|
54
|
+
- [Shared input options](#shared-input-options)
|
55
55
|
- [Optional inputs](#optional-inputs)
|
56
56
|
- [Translations](#translations)
|
57
57
|
- [Credits](#credits)
|
@@ -63,18 +63,17 @@ handles your verbs.
|
|
63
63
|
Add it to your Gemfile:
|
64
64
|
|
65
65
|
``` rb
|
66
|
-
gem 'active_interaction', '~>
|
66
|
+
gem 'active_interaction', '~> 5.0'
|
67
67
|
```
|
68
68
|
|
69
69
|
Or install it manually:
|
70
70
|
|
71
71
|
``` sh
|
72
|
-
$ gem install active_interaction --version '~>
|
72
|
+
$ gem install active_interaction --version '~> 5.0'
|
73
73
|
```
|
74
74
|
|
75
75
|
This project uses [Semantic Versioning][]. Check out [GitHub releases][] for a
|
76
|
-
detailed list of changes.
|
77
|
-
announcement post][].
|
76
|
+
detailed list of changes.
|
78
77
|
|
79
78
|
## Basic usage
|
80
79
|
|
@@ -89,7 +88,7 @@ you need to do two things:
|
|
89
88
|
2. **Define your business logic.** Do this by implementing the `#execute`
|
90
89
|
method. Each input you defined will be available as the type you specified.
|
91
90
|
If any of the inputs are invalid, `#execute` won't be run. Filters are
|
92
|
-
responsible for
|
91
|
+
responsible for checking your inputs. Check out [the validations
|
93
92
|
section](#validations) if you need more than that.
|
94
93
|
|
95
94
|
That covers the basics. Let's put it all together into a simple example that
|
@@ -141,7 +140,7 @@ Square.run!(x: 2.1)
|
|
141
140
|
|
142
141
|
### Validations
|
143
142
|
|
144
|
-
ActiveInteraction
|
143
|
+
ActiveInteraction checks your inputs. Often you'll want more than that.
|
145
144
|
For instance, you may want an input to be a string with at least one
|
146
145
|
non-whitespace character. Instead of writing your own validation for that, you
|
147
146
|
can use validations from ActiveModel.
|
@@ -164,7 +163,7 @@ end
|
|
164
163
|
```
|
165
164
|
|
166
165
|
When you run this interaction, two things will happen. **First
|
167
|
-
ActiveInteraction will
|
166
|
+
ActiveInteraction will check your inputs. Then ActiveModel will validate
|
168
167
|
them.** If both of those are happy, it will be executed.
|
169
168
|
|
170
169
|
``` rb
|
@@ -261,6 +260,32 @@ array :managers do
|
|
261
260
|
end
|
262
261
|
```
|
263
262
|
|
263
|
+
Errors that occur will be indexed based on the Rails configuration setting
|
264
|
+
`index_nested_attribute_errors`. You can also manually override this setting
|
265
|
+
with the `:index_errors` option. In this state is is possible to get multiple
|
266
|
+
errors from a single filter.
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
class ArrayInteraction < ActiveInteraction::Base
|
270
|
+
array :favorite_numbers, index_errors: true do
|
271
|
+
integer
|
272
|
+
end
|
273
|
+
|
274
|
+
def execute
|
275
|
+
favorite_numbers
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
ArrayInteraction.run(favorite_numbers: [8, 'bazillion']).errors.details
|
280
|
+
=> {:"favorite_numbers[1]"=>[{:error=>:invalid_type, :type=>"array"}]}
|
281
|
+
```
|
282
|
+
|
283
|
+
With `:index_errors` set to `false` the error would have been:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
{:favorite_numbers=>[{:error=>:invalid_type, :type=>"array"}]}
|
287
|
+
```
|
288
|
+
|
264
289
|
### Boolean
|
265
290
|
|
266
291
|
Boolean filters convert the strings `"1"`, `"true"`, and `"on"`
|
@@ -464,8 +489,8 @@ not an instance of the class or one of its subclasses. The `converter` option
|
|
464
489
|
accepts a symbol that specifies a class method on the object class or a proc.
|
465
490
|
Both will be passed the value and any errors thrown inside the converter will
|
466
491
|
cause the value to be considered invalid. Any returned value that is not the
|
467
|
-
correct class will also be treated as invalid.
|
468
|
-
|
492
|
+
correct class will also be treated as invalid. Any `default` that is not an
|
493
|
+
instance of the class or subclass and is not `nil` will also be converted.
|
469
494
|
|
470
495
|
``` rb
|
471
496
|
class ObjectInteraction < ActiveInteraction::Base
|
@@ -492,8 +517,9 @@ of its subclasses) or a value that can be used to locate an instance of the
|
|
492
517
|
object. If the value does not match, it will call `find` on the class of the
|
493
518
|
record. This is particularly useful when working with ActiveRecord objects.
|
494
519
|
Like an object filter, the class is derived from the name passed but can be
|
495
|
-
specified with the `class` option.
|
496
|
-
also be found.
|
520
|
+
specified with the `class` option. Any `default` that is not an instance of the
|
521
|
+
class or subclass and is not `nil` will also be found. Blank strings passed in
|
522
|
+
will be treated as `nil`.
|
497
523
|
|
498
524
|
``` rb
|
499
525
|
class RecordInteraction < ActiveInteraction::Base
|
@@ -736,10 +762,7 @@ resourceful actions.
|
|
736
762
|
|
737
763
|
We recommend putting your interactions in `app/interactions`. It's also very
|
738
764
|
helpful to group them by model. That way you can look in
|
739
|
-
`app/interactions/accounts` for all the ways you can interact with accounts.
|
740
|
-
order to use this structure add
|
741
|
-
`config.autoload_paths += Dir.glob("#{config.root}/app/interactions/*")` in
|
742
|
-
your `application.rb`
|
765
|
+
`app/interactions/accounts` for all the ways you can interact with accounts.
|
743
766
|
|
744
767
|
```
|
745
768
|
- app/
|
@@ -842,7 +865,7 @@ end
|
|
842
865
|
```
|
843
866
|
|
844
867
|
Note that it's perfectly fine to add errors during execution. Not all errors
|
845
|
-
have to come from
|
868
|
+
have to come from checking or validation.
|
846
869
|
|
847
870
|
#### New
|
848
871
|
|
@@ -1025,7 +1048,7 @@ interaction's lifecycle.
|
|
1025
1048
|
|
1026
1049
|
``` rb
|
1027
1050
|
class Increment < ActiveInteraction::Base
|
1028
|
-
set_callback :
|
1051
|
+
set_callback :filter, :before, -> { puts 'before filter' }
|
1029
1052
|
|
1030
1053
|
integer :x
|
1031
1054
|
|
@@ -1047,7 +1070,7 @@ class Increment < ActiveInteraction::Base
|
|
1047
1070
|
end
|
1048
1071
|
|
1049
1072
|
Increment.run!(x: 1)
|
1050
|
-
# before
|
1073
|
+
# before filter
|
1051
1074
|
# after validate
|
1052
1075
|
# >>>
|
1053
1076
|
# executing
|
@@ -1055,7 +1078,7 @@ Increment.run!(x: 1)
|
|
1055
1078
|
# => 2
|
1056
1079
|
```
|
1057
1080
|
|
1058
|
-
In order, the available callbacks are `
|
1081
|
+
In order, the available callbacks are `filter`, `validate`, and `execute`.
|
1059
1082
|
You can set `before`, `after`, or `around` on any of them.
|
1060
1083
|
|
1061
1084
|
### Composition
|
@@ -1178,7 +1201,7 @@ errors.
|
|
1178
1201
|
``` rb
|
1179
1202
|
outcome = BuyItem.run(item: 'Thing', options: { gift_wrapped: 'yes' })
|
1180
1203
|
outcome.errors.messages
|
1181
|
-
# => {:credit_card=>["is required"], :item=>["is not a valid object"], :options=>["
|
1204
|
+
# => {:credit_card=>["is required"], :item=>["is not a valid object"], :"options.gift_wrapped"=>["is not a valid boolean"]}
|
1182
1205
|
```
|
1183
1206
|
|
1184
1207
|
Determining the type of error based on the string is difficult if not
|
@@ -1187,7 +1210,7 @@ the same list of errors with a testable label representing the error.
|
|
1187
1210
|
|
1188
1211
|
``` rb
|
1189
1212
|
outcome.errors.details
|
1190
|
-
# => {:credit_card=>[{:error=>:missing}], :item=>[{:type=>"object"
|
1213
|
+
# => {:credit_card=>[{:error=>:missing}], :item=>[{:error=>:invalid_type, :type=>"object"}], :"options.gift_wrapped"=>[{:error=>:invalid_type, :type=>"boolean"}]}
|
1191
1214
|
```
|
1192
1215
|
|
1193
1216
|
Detailed errors can also be manually added during the execute call by passing a
|
@@ -1337,7 +1360,7 @@ used to define the inputs on your interaction will relay type information to
|
|
1337
1360
|
these gems. As a result, form fields will automatically use the appropriate
|
1338
1361
|
input type.
|
1339
1362
|
|
1340
|
-
###
|
1363
|
+
### Shared input options
|
1341
1364
|
|
1342
1365
|
It can be convenient to apply the same options to a bunch of inputs. One common
|
1343
1366
|
use case is making many inputs optional. Instead of setting `default: nil` on
|
@@ -1358,8 +1381,8 @@ Optional inputs can be defined by using the `:default` option as described in
|
|
1358
1381
|
are merged to create `inputs`. There are times where it is useful to know
|
1359
1382
|
whether a value was passed to `run` or the result of a filter default. In
|
1360
1383
|
particular, it is useful when `nil` is an acceptable value. For example, you
|
1361
|
-
may optionally track your users' birthdays. You can use the `given?` predicate
|
1362
|
-
to see if an input was even passed to `run`. With `given?` you can also check
|
1384
|
+
may optionally track your users' birthdays. You can use the `inputs.given?` predicate
|
1385
|
+
to see if an input was even passed to `run`. With `inputs.given?` you can also check
|
1363
1386
|
the input of a hash or array filter by passing a series of keys or indexes to
|
1364
1387
|
check.
|
1365
1388
|
|
@@ -1370,7 +1393,7 @@ class UpdateUser < ActiveInteraction::Base
|
|
1370
1393
|
default: nil
|
1371
1394
|
|
1372
1395
|
def execute
|
1373
|
-
user.birthday = birthday if given?(:birthday)
|
1396
|
+
user.birthday = birthday if inputs.given?(:birthday)
|
1374
1397
|
errors.merge!(user.errors) unless user.save
|
1375
1398
|
user
|
1376
1399
|
end
|
@@ -1424,7 +1447,6 @@ hsilgne:
|
|
1424
1447
|
errors:
|
1425
1448
|
messages:
|
1426
1449
|
invalid: dilavni si
|
1427
|
-
invalid_nested: (%{value} <= %{name}) eulav detsen dilavni na sah
|
1428
1450
|
invalid_type: '%{type} dilav a ton si'
|
1429
1451
|
missing: deriuqer si
|
1430
1452
|
```
|
@@ -1444,6 +1466,19 @@ I18nInteraction.run(name: false).errors.messages[:name]
|
|
1444
1466
|
# => ["gnirts dilav a ton si"]
|
1445
1467
|
```
|
1446
1468
|
|
1469
|
+
Everything else works like an `activerecord` entry. For example, to rename an
|
1470
|
+
attribute you can use `attributes`.
|
1471
|
+
|
1472
|
+
Here we'll rename the `num` attribute on an interaction named `product`:
|
1473
|
+
|
1474
|
+
``` yml
|
1475
|
+
en:
|
1476
|
+
active_interaction:
|
1477
|
+
attributes:
|
1478
|
+
product:
|
1479
|
+
num: 'Number'
|
1480
|
+
```
|
1481
|
+
|
1447
1482
|
## Credits
|
1448
1483
|
|
1449
1484
|
ActiveInteraction is brought to you by [Aaron Lasseigne][].
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveInteraction
|
4
|
+
# Represents a processed array input.
|
5
|
+
class ArrayInput < Input
|
6
|
+
# @private
|
7
|
+
def initialize(filter, value: nil, error: nil, index_errors: false, children: [])
|
8
|
+
super(filter, value: value, error: error)
|
9
|
+
|
10
|
+
@filter = filter
|
11
|
+
@index_errors = index_errors
|
12
|
+
@children = children
|
13
|
+
end
|
14
|
+
|
15
|
+
# @overload children
|
16
|
+
# Child inputs if a nested filter is used.
|
17
|
+
#
|
18
|
+
# @return [Array<Input, ArrayInput, HashInput>]
|
19
|
+
attr_reader :children
|
20
|
+
|
21
|
+
# Any errors that occurred during processing.
|
22
|
+
#
|
23
|
+
# @return [Filter::Error]
|
24
|
+
def errors
|
25
|
+
return @errors if defined?(@errors)
|
26
|
+
|
27
|
+
return @errors = super if @error
|
28
|
+
|
29
|
+
child_errors = get_errors_by_index(children)
|
30
|
+
|
31
|
+
return @errors = super if child_errors.empty?
|
32
|
+
|
33
|
+
@errors ||=
|
34
|
+
if @index_errors
|
35
|
+
child_errors.map do |(error, i)|
|
36
|
+
name = attach_child_name(:"#{@filter.name}[#{i}]", error)
|
37
|
+
Filter::Error.new(error.filter, error.type, name: name)
|
38
|
+
end.freeze
|
39
|
+
else
|
40
|
+
error, = child_errors.first
|
41
|
+
[Filter::Error.new(@filter, error.type)].freeze
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def get_errors_by_index(children)
|
48
|
+
children.flat_map.with_index do |child, i|
|
49
|
+
child.errors.map do |error|
|
50
|
+
[error, i]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def attach_child_name(name, error)
|
56
|
+
return name unless error.name.present?
|
57
|
+
|
58
|
+
if children_are_arrays?(children)
|
59
|
+
:"#{name}#{error.name.to_s.sub(/\A[^\[]*/, '')}"
|
60
|
+
elsif children_are_hashes?(children)
|
61
|
+
:"#{name}.#{error.name.to_s[1..]}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def children_are_arrays?(children)
|
66
|
+
return @children_are_arrays if defined?(@children_are_arrays)
|
67
|
+
|
68
|
+
@children_are_arrays = children.first&.is_a?(ArrayInput)
|
69
|
+
end
|
70
|
+
|
71
|
+
def children_are_hashes?(children)
|
72
|
+
return @children_are_hashes if defined?(@children_are_hashes)
|
73
|
+
|
74
|
+
@children_are_hashes = children.first&.is_a?(HashInput)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -30,7 +30,7 @@ module ActiveInteraction
|
|
30
30
|
include ActiveRecordable
|
31
31
|
include Runnable
|
32
32
|
|
33
|
-
define_callbacks :
|
33
|
+
define_callbacks :filter
|
34
34
|
|
35
35
|
class << self
|
36
36
|
include Hashable
|
@@ -43,7 +43,7 @@ module ActiveInteraction
|
|
43
43
|
#
|
44
44
|
# Runs validations and if there are no errors it will call {#execute}.
|
45
45
|
#
|
46
|
-
# @param
|
46
|
+
# @param input [Hash, ActionController::Parameters]
|
47
47
|
#
|
48
48
|
# @return [Base]
|
49
49
|
|
@@ -88,7 +88,8 @@ module ActiveInteraction
|
|
88
88
|
# rubocop:enable Naming/MemoizedInstanceVariableName
|
89
89
|
end
|
90
90
|
|
91
|
-
|
91
|
+
private
|
92
|
+
|
92
93
|
# rubocop:disable Style/MissingRespondToMissing
|
93
94
|
def method_missing(*args, &block)
|
94
95
|
super do |klass, names, options|
|
@@ -99,8 +100,6 @@ module ActiveInteraction
|
|
99
100
|
end
|
100
101
|
# rubocop:enable Style/MissingRespondToMissing
|
101
102
|
|
102
|
-
private
|
103
|
-
|
104
103
|
# @param klass [Class]
|
105
104
|
# @param name [Symbol]
|
106
105
|
# @param options [Hash]
|
@@ -162,7 +161,9 @@ module ActiveInteraction
|
|
162
161
|
def initialize(inputs = {})
|
163
162
|
@_interaction_raw_inputs = inputs
|
164
163
|
|
165
|
-
|
164
|
+
@_interaction_inputs = Inputs.new(inputs, self) do |name, input|
|
165
|
+
public_send("#{name}=", input.value)
|
166
|
+
end
|
166
167
|
end
|
167
168
|
|
168
169
|
# @!method compose(other, inputs = {})
|
@@ -186,113 +187,28 @@ module ActiveInteraction
|
|
186
187
|
# Returns the inputs provided to {.run} or {.run!} after being cast based
|
187
188
|
# on the filters in the class.
|
188
189
|
#
|
189
|
-
# @return [
|
190
|
+
# @return [Inputs] All expected inputs passed to {.run} or {.run!}.
|
190
191
|
def inputs
|
191
192
|
@_interaction_inputs
|
192
193
|
end
|
193
194
|
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
# by passing them in series. Arrays can be checked in the same manor as
|
198
|
-
# hashes by passing an index.
|
199
|
-
#
|
200
|
-
# @example
|
201
|
-
# class Example < ActiveInteraction::Base
|
202
|
-
# integer :x, default: nil
|
203
|
-
# def execute; given?(:x) end
|
204
|
-
# end
|
205
|
-
# Example.run!() # => false
|
206
|
-
# Example.run!(x: nil) # => true
|
207
|
-
# Example.run!(x: rand) # => true
|
208
|
-
#
|
209
|
-
# @example Nested checks
|
210
|
-
# class Example < ActiveInteraction::Base
|
211
|
-
# hash :x, default: {} do
|
212
|
-
# integer :y, default: nil
|
213
|
-
# end
|
214
|
-
# array :a, default: [] do
|
215
|
-
# integer
|
216
|
-
# end
|
217
|
-
# def execute; given?(:x, :y) || given?(:a, 2) end
|
218
|
-
# end
|
219
|
-
# Example.run!() # => false
|
220
|
-
# Example.run!(x: nil) # => false
|
221
|
-
# Example.run!(x: {}) # => false
|
222
|
-
# Example.run!(x: { y: nil }) # => true
|
223
|
-
# Example.run!(x: { y: rand }) # => true
|
224
|
-
# Example.run!(a: [1, 2]) # => false
|
225
|
-
# Example.run!(a: [1, 2, 3]) # => true
|
226
|
-
#
|
227
|
-
# @param input [#to_sym]
|
228
|
-
#
|
229
|
-
# @return [Boolean]
|
230
|
-
#
|
231
|
-
# @since 2.1.0
|
232
|
-
# rubocop:disable all
|
233
|
-
def given?(input, *rest)
|
234
|
-
filter_level = self.class
|
235
|
-
input_level = @_interaction_raw_inputs
|
236
|
-
|
237
|
-
[input, *rest].each do |key_or_index|
|
238
|
-
if key_or_index.is_a?(Symbol) || key_or_index.is_a?(String)
|
239
|
-
key = key_or_index.to_sym
|
240
|
-
key_to_s = key_or_index.to_s
|
241
|
-
filter_level = filter_level.filters[key]
|
242
|
-
|
243
|
-
break false if filter_level.nil? || input_level.nil?
|
244
|
-
if filter_level.accepts_grouped_inputs?
|
245
|
-
break false unless input_level.key?(key) || input_level.key?(key_to_s) || Inputs.keys_for_group?(input_level.keys, key)
|
246
|
-
else
|
247
|
-
break false unless input_level.key?(key) || input_level.key?(key_to_s)
|
248
|
-
end
|
249
|
-
|
250
|
-
input_level = input_level[key] || input_level[key_to_s]
|
251
|
-
else
|
252
|
-
index = key_or_index
|
253
|
-
filter_level = filter_level.filters.first.last
|
254
|
-
|
255
|
-
break false if filter_level.nil? || input_level.nil?
|
256
|
-
break false unless index.between?(-input_level.size, input_level.size - 1)
|
257
|
-
|
258
|
-
input_level = input_level[index]
|
259
|
-
end
|
260
|
-
end && true
|
195
|
+
# @private
|
196
|
+
def read_attribute_for_validation(attribute)
|
197
|
+
super(errors.local_attribute(attribute))
|
261
198
|
end
|
262
|
-
# rubocop:enable all
|
263
199
|
|
264
200
|
protected
|
265
201
|
|
266
202
|
def run_validations!
|
267
|
-
|
203
|
+
filter
|
268
204
|
|
269
205
|
super if errors.empty?
|
270
206
|
end
|
271
207
|
|
272
208
|
private
|
273
209
|
|
274
|
-
def
|
275
|
-
|
276
|
-
|
277
|
-
self.class.filters.each do |name, filter|
|
278
|
-
value =
|
279
|
-
begin
|
280
|
-
filter.clean(inputs[name], self)
|
281
|
-
rescue InvalidValueError, MissingValueError, NoDefaultError
|
282
|
-
# #type_check will add errors if appropriate.
|
283
|
-
# We'll get the original value for the error.
|
284
|
-
inputs[name]
|
285
|
-
end
|
286
|
-
|
287
|
-
@_interaction_inputs[name] = value
|
288
|
-
public_send("#{name}=", value)
|
289
|
-
end
|
290
|
-
|
291
|
-
@_interaction_inputs.freeze
|
292
|
-
end
|
293
|
-
|
294
|
-
def type_check
|
295
|
-
run_callbacks(:type_check) do
|
210
|
+
def filter
|
211
|
+
run_callbacks(:filter) do
|
296
212
|
Validation.validate(self, self.class.filters, inputs).each do |attr, type, kwargs = {}|
|
297
213
|
errors.add(attr, type, **kwargs)
|
298
214
|
end
|
@@ -17,15 +17,15 @@ module ActiveInteraction
|
|
17
17
|
# end
|
18
18
|
#
|
19
19
|
# Interaction.new.column_for_attribute(:email)
|
20
|
-
# # => #<ActiveInteraction::
|
20
|
+
# # => #<ActiveInteraction::Filter::Column:0x007faebeb2a6c8 @type=:string>
|
21
21
|
#
|
22
22
|
# Interaction.new.column_for_attribute(:not_a_filter)
|
23
23
|
# # => nil
|
24
24
|
#
|
25
|
-
# @return [
|
25
|
+
# @return [Filter::Column, nil]
|
26
26
|
def column_for_attribute(name)
|
27
27
|
filter = self.class.filters[name]
|
28
|
-
|
28
|
+
Filter::Column.intern(filter.database_column_type) if filter
|
29
29
|
end
|
30
30
|
|
31
31
|
# Returns true if a filter of that name exists.
|
@@ -7,6 +7,8 @@ module ActiveInteraction
|
|
7
7
|
module Missable
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
+
private
|
11
|
+
|
10
12
|
# @param slug [Symbol]
|
11
13
|
#
|
12
14
|
# @yield [klass, args, options]
|
@@ -26,8 +28,6 @@ module ActiveInteraction
|
|
26
28
|
self
|
27
29
|
end
|
28
30
|
|
29
|
-
private
|
30
|
-
|
31
31
|
# @param slug [Symbol]
|
32
32
|
#
|
33
33
|
# @return [Filter, nil]
|