makwa 1.0.0 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/doc/about_interactions.md +12 -4
- data/doc/features_and_design_considerations.md +93 -1
- data/doc/how_to_release_new_version.md +2 -1
- data/doc/usage_examples.md +9 -3
- data/lib/makwa/interaction.rb +6 -3
- data/lib/makwa/returning_interaction.rb +1 -1
- data/lib/makwa/version.rb +1 -1
- data/makwa.gemspec +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 347d964215a73774af300989657a8b00cc8ca7cfc33ef3574fc27ab9b2a2b70e
|
4
|
+
data.tar.gz: 509297308f4826aae87b1ebe4b5d1bc0b92c481e0a32b8835fc90bf4c7691632
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cbfa68e12c36034d16cc25f60150b165fdb8cc43d45eadbbd7ecf8be4d3d60d8254832e62ce5c6a93132fb83fa7aec2b5f413a87b0b56e147fb777abb037f03
|
7
|
+
data.tar.gz: 7f9252de4f2b153d4a06369491615cf02b247666438f49047f80700a2237c40ed5f743aa4e8eabe452426e1de3ebe7d74b108a41bf9dc4695fd587bd36dc34ce
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.0.2] - 2025-01-05
|
4
|
+
|
5
|
+
- Updates #return_if_errors! to not include errors in exception object. This addresses an issue where errors
|
6
|
+
are duplicated under the base key.
|
7
|
+
- Adds tests for failing composed interactions.
|
8
|
+
|
9
|
+
## [1.0.1] - 2022-12-01
|
10
|
+
|
11
|
+
- Fixes reference to nested class (symbol -> string)
|
12
|
+
|
3
13
|
## [1.0.0] - 2022-11-26
|
4
14
|
|
5
15
|
- Upgrades ActiveInteraction from 4.x to 5.2.x. Please see the [ActiveInteraction CHANGELOG](https://github.com/AaronLasseigne/active_interaction/blob/main/CHANGELOG.md) for what has changed between v. 4.x and 5. There are some breaking changes.
|
data/doc/about_interactions.md
CHANGED
@@ -4,10 +4,18 @@
|
|
4
4
|
|
5
5
|
When to use an interaction:
|
6
6
|
|
7
|
-
*
|
8
|
-
*
|
9
|
-
*
|
10
|
-
*
|
7
|
+
* Integration with Rails CRUD forms:
|
8
|
+
* FormObject preparation.
|
9
|
+
* FormParams sanitization/transformation
|
10
|
+
* special validations
|
11
|
+
* CRUD persistence operations
|
12
|
+
* post-persistence actions like sending an email, or triggering another interaction
|
13
|
+
* Decoupling behavior from ActiveRecord classes:
|
14
|
+
* Replace **ALL** ActiveRecord `:after_...` callbacks with interactions. This refers to all after callbacks, not just save.
|
15
|
+
* Replace **MOST** ActiveRecord `:before_...` callbacks with interactions. This refers to all `:before_…` callbacks, not just save. An exception that can remain as a callback could be a `:before_validation` callback to sanitize an email address (strip surrounding whitespace, lower case), however, if there is already an interaction to create/update a user, you may as well do it in the interaction.
|
16
|
+
* Replace Model instance and class methods that implement complex behaviours with interactions. Note that you can still use the Model methods as interface, however, the implementation should live in an interaction.
|
17
|
+
* Implement complex domain behaviours by composing sub tasks into higher level processes.
|
18
|
+
* Wrap a 3rd party service so that we can swap it out in a single place if needed.
|
11
19
|
|
12
20
|
## ReturningInteractions
|
13
21
|
|
@@ -1,3 +1,95 @@
|
|
1
1
|
# Features and design considerations
|
2
2
|
|
3
|
-
|
3
|
+
## Input validation and coercion
|
4
|
+
|
5
|
+
All input arguments are validated and coerced before the interaction is invoked. Depending on the use case we can use the following validations:
|
6
|
+
|
7
|
+
* ActiveInteraction input filters - Check for presence of input argument keys, their types, and can set default values for optional input arguments.
|
8
|
+
* ActiveModel validations in the Interaction. - Use the full power of ActiveModel::Validation to check for specific values, or higher level business rules that are specific to the interaction.
|
9
|
+
* ActiveModel validations in the ActiveRecord instance passed to a ReturningInteraction - For shared validations that apply to all related interactions.
|
10
|
+
* Dry-validation based contracts for advanced validations of deeply nested data structures.
|
11
|
+
|
12
|
+
The validation solutions listed above offer the following features:
|
13
|
+
|
14
|
+
* Type checking.
|
15
|
+
* Ability to implement complex validation rules.
|
16
|
+
* Ability to look at other input args when validating an arg.
|
17
|
+
* Composability: Input validations can be composed to prevent duplication and allow re-use. Dry validation offers this temporary workaround for composing rules: https://github.com/dry-rb/dry-validation/issues/593#issuecomment-631597226
|
18
|
+
* Type coercion, e.g., for Rails request params that need to be cast from String to Integer.
|
19
|
+
* Runtime validation (since we don’t know what arguments are passed to an Interaction at compile time).
|
20
|
+
* Default values can be assigned when using ActiveInteraction input filters.
|
21
|
+
|
22
|
+
Below are some options for input argument validation:
|
23
|
+
|
24
|
+
* ActiveModel Validations: https://api.rubyonrails.org/classes/ActiveModel/Validations.html
|
25
|
+
* ActiveInteraction filters: https://github.com/AaronLasseigne/active_interaction#filters
|
26
|
+
* dry-validation contracts: https://dry-rb.org/gems/dry-validation/
|
27
|
+
|
28
|
+
## Composability
|
29
|
+
|
30
|
+
Interactions can be composed to facilitate re-use and modularity.
|
31
|
+
|
32
|
+
* Errors raised in composed interactions are merged into the parent interaction.
|
33
|
+
* Execution in parent interaction stops when a composed interaction fails.
|
34
|
+
* Composed interactions act like the #run! bang method with exception handling built in.
|
35
|
+
|
36
|
+
Code example:
|
37
|
+
|
38
|
+
```
|
39
|
+
def execute
|
40
|
+
r1 = compose(Nested::Service1, arg1: 123, arg2: "a string")
|
41
|
+
r2 = compose(Nested::Service2, arg1: r1)
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Note that ActiveInteraction input filters can also be reused via `import_filters OtherInteraction`.
|
46
|
+
|
47
|
+
## Exception handling and error reporting
|
48
|
+
|
49
|
+
* Errors (not Ruby Exceptions!) added in an interaction are accessible to the caller for testing and notification purposes.
|
50
|
+
* Errors are addable manually inside the interaction via errors.add(:base, text: "something went wrong", key: :something_went_wrong).
|
51
|
+
* Errors are mergeable from other sources, e.g., ActiveRecord objects or nested interactions via errors.merge!(@user.errors).
|
52
|
+
* Early exit of the interaction is possible if an unrecoverable error condition is detected. E.g., via return_if_errors! or return.
|
53
|
+
|
54
|
+
## Localization
|
55
|
+
|
56
|
+
Success and error messages are customizable via Rails' default I18n mechanisms.
|
57
|
+
|
58
|
+
## Integration with Rails forms
|
59
|
+
|
60
|
+
ReturningInteractions, are a specialized subclass of ActiveInteraction, work well with processing params submitted from Rails forms, and with possible re-rendering of the form if there are any errors.
|
61
|
+
|
62
|
+
## Dependency injection
|
63
|
+
|
64
|
+
Dependencies can be injected into an interaction, mostly for testing. Example: We inject a fake `Git` library when testing code that makes git commits. Other examples: `File`, `Time`, `TwitterApi`, etc.
|
65
|
+
|
66
|
+
## Serializability
|
67
|
+
|
68
|
+
Invocation of an interaction, with its input arguments, can be serialized in a simple, robust, and performant way. We accomplish this by limiting input arguments to basic Ruby types: Hashes, Arrays, Strings, Numbers, and Booleans.
|
69
|
+
|
70
|
+
## Logging
|
71
|
+
|
72
|
+
Interactions print detailed info to the log. Output includes:
|
73
|
+
|
74
|
+
* Name of invoked interaction
|
75
|
+
* caller
|
76
|
+
* input args
|
77
|
+
* any errors
|
78
|
+
|
79
|
+
Example:
|
80
|
+
|
81
|
+
```
|
82
|
+
Executing interaction Niiwin::NwLoader::InitialLoad (id#70361484811280)
|
83
|
+
↳ inputs: {} (id#70361484811280)
|
84
|
+
Executing interaction Niiwin::NwLoader::NwConfigs::Load (id#70361484766000)
|
85
|
+
↳ called from niiwin/nw_loader/initial_load.rb:14:in `initial_load_nw_config' (id#70361484766000)
|
86
|
+
↳ inputs: {} (id#70361484766000)
|
87
|
+
↳ outcome: succeeded (id#70361484766000)
|
88
|
+
Executing interaction Niiwin::NwLoader::NwConfigs::Validate (id#70361476717620)
|
89
|
+
↳ called from niiwin/nw_loader/initial_load.rb:15:in `initial_load_nw_config' (id#70361476717620)
|
90
|
+
↳ inputs: {} (id#70361476717620)
|
91
|
+
↳ outcome: succeeded (id#70361476717620)
|
92
|
+
↳ outcome: succeeded (id#70361484811280)
|
93
|
+
```
|
94
|
+
|
95
|
+
You can turn off logging by overriding the `ApplicationInteraction#debug` method.
|
@@ -9,5 +9,6 @@
|
|
9
9
|
* Commit the change with "Bump makwa to <version>"
|
10
10
|
* Tag the commit of the new version with `v<version>`
|
11
11
|
* Push the changes
|
12
|
+
* Build the gem with `gem build`
|
12
13
|
* Distribute new release
|
13
|
-
* `gem push makwa` - This will push the new release to rubygems.org.
|
14
|
+
* `gem push makwa-<version>.gem` - This will push the new release to rubygems.org.
|
data/doc/usage_examples.md
CHANGED
@@ -15,7 +15,7 @@ end
|
|
15
15
|
```
|
16
16
|
|
17
17
|
```ruby
|
18
|
-
# app/interactions/
|
18
|
+
# app/interactions/application_returning_interaction.rb
|
19
19
|
class ApplicationReturningInteraction < Makwa::ReturningInteraction
|
20
20
|
def debug(txt)
|
21
21
|
# Uncomment the next line for detailed debug output
|
@@ -50,6 +50,12 @@ module Infrastructure
|
|
50
50
|
end
|
51
51
|
```
|
52
52
|
|
53
|
+
You can then use this interaction to send emails:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
Infrastructure::SendEmail.run!(recipient_email: "email@test.com", subject: "Email Subject", body: "Email Body")
|
57
|
+
```
|
58
|
+
|
53
59
|
## Implement a ReturningInteraction to create a user
|
54
60
|
|
55
61
|
```ruby
|
@@ -141,7 +147,7 @@ class UsersController < ApplicationController
|
|
141
147
|
end
|
142
148
|
|
143
149
|
def update
|
144
|
-
@user = Users::
|
150
|
+
@user = Users::Update.run_returning!(
|
145
151
|
{user: @user}.merge(params.fetch(:user, {}))
|
146
152
|
)
|
147
153
|
|
@@ -187,7 +193,7 @@ Interactions follow these conventions:
|
|
187
193
|
* Makes it easy to pass test data into an interaction.
|
188
194
|
* Makes it easy to serialize the invocation of an interaction, e.g., for a Sidekiq background job.
|
189
195
|
* We do not pass in ActiveRecord instances, but their ids instead. We rely on ActiveRecord caching to prevent multiple DB reads when passing the same record id into nested interactions. Exceptions:
|
190
|
-
* You can pass a
|
196
|
+
* You can pass a descendant of ActiveModel, e.g., an ActiveRecord instance as the returned input to a ReturningInteraction. Use the record input filter type. It accepts both the ActiveRecord instance, as well as its id attribute. That way, you can still pass in basic Ruby types, e.g., in the console when invoking the interaction.
|
191
197
|
* In some use cases with nested interactions, we may choose to pass in an ActiveRecord instance to work around persistence concerns.
|
192
198
|
* When an interaction is concerned with an ActiveRecord instance, we pass the record’s id under the :id hash key (unless it’s a ReturningInteraction).
|
193
199
|
|
data/lib/makwa/interaction.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Makwa
|
4
4
|
class Interaction < ::ActiveInteraction::Base
|
5
|
-
class Interrupt < Object.const_get(
|
5
|
+
class Interrupt < Object.const_get("::ActiveInteraction::Interrupt")
|
6
6
|
end
|
7
7
|
|
8
8
|
#
|
@@ -20,7 +20,7 @@ module Makwa
|
|
20
20
|
|
21
21
|
# Exits early if there are any errors.
|
22
22
|
def return_if_errors!
|
23
|
-
raise(Interrupt,
|
23
|
+
raise(Interrupt, ActiveInteraction::Errors.new(self)) if errors_any?
|
24
24
|
end
|
25
25
|
|
26
26
|
#
|
@@ -51,7 +51,10 @@ module Makwa
|
|
51
51
|
# @return [Array<String>] the callstack containing interactions only, starting with the immediate caller.
|
52
52
|
def calling_interactions
|
53
53
|
@calling_interactions ||= caller.find_all { |e|
|
54
|
-
e.index("/app/interactions/")
|
54
|
+
e.index("/app/interactions/") \
|
55
|
+
&& !e.index(__FILE__) \
|
56
|
+
&& !e.index("/returning_interaction.rb") \
|
57
|
+
&& !e.index("`debug'")
|
55
58
|
}
|
56
59
|
end
|
57
60
|
|
data/lib/makwa/version.rb
CHANGED
data/makwa.gemspec
CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
end
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
|
31
|
-
spec.add_dependency "active_interaction", "~> 5.
|
31
|
+
spec.add_dependency "active_interaction", "~> 5.4.0"
|
32
32
|
|
33
33
|
spec.add_development_dependency "standard"
|
34
34
|
spec.add_development_dependency "byebug"
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: makwa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jo Hund
|
8
8
|
- Fabio Papa
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-01-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: active_interaction
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: 5.
|
20
|
+
version: 5.4.0
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: 5.
|
27
|
+
version: 5.4.0
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: standard
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
|
-
description:
|
56
|
+
description:
|
57
57
|
email:
|
58
58
|
- jo@animikii.com
|
59
59
|
- fabio.papa@animikii.com
|
@@ -85,7 +85,7 @@ metadata:
|
|
85
85
|
homepage_uri: https://github.com/animikii/makwa
|
86
86
|
source_code_uri: https://github.com/animikii/makwa
|
87
87
|
changelog_uri: https://github.com/animikii/makwa/CHANGELOG.md
|
88
|
-
post_install_message:
|
88
|
+
post_install_message:
|
89
89
|
rdoc_options: []
|
90
90
|
require_paths:
|
91
91
|
- lib
|
@@ -100,8 +100,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
100
|
- !ruby/object:Gem::Version
|
101
101
|
version: '0'
|
102
102
|
requirements: []
|
103
|
-
rubygems_version: 3.
|
104
|
-
signing_key:
|
103
|
+
rubygems_version: 3.4.20
|
104
|
+
signing_key:
|
105
105
|
specification_version: 4
|
106
106
|
summary: Interactions for Ruby on Rails apps.
|
107
107
|
test_files: []
|