active_interaction 4.1.0 → 5.0.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/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
|
[](https://rubygems.org/gems/active_interaction)
|
7
7
|
[](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]
|