use_cases 0.3.7 → 1.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/Gemfile.lock +1 -1
- data/README.md +108 -179
- data/lib/use_case.rb +2 -8
- data/lib/use_cases/dsl.rb +0 -14
- data/lib/use_cases/events/publish_job.rb +13 -0
- data/lib/use_cases/events/publisher.rb +50 -0
- data/lib/use_cases/events/subscriber.rb +13 -0
- data/lib/use_cases/module_optins/authorized.rb +30 -0
- data/lib/use_cases/module_optins/prepared.rb +22 -0
- data/lib/use_cases/module_optins/publishing.rb +47 -0
- data/lib/use_cases/module_optins/transactional.rb +28 -0
- data/lib/use_cases/module_optins/validated.rb +112 -0
- data/lib/use_cases/module_optins.rb +22 -24
- data/lib/use_cases/{step_result.rb → result.rb} +16 -10
- data/lib/use_cases/rspec/matchers.rb +5 -3
- data/lib/use_cases/stack_runner.rb +4 -15
- data/lib/use_cases/step_active_job_adapter.rb +1 -1
- data/lib/use_cases/step_adapters/abstract.rb +44 -39
- data/lib/use_cases/step_adapters.rb +1 -0
- data/lib/use_cases/version.rb +1 -1
- data/lib/use_cases.rb +12 -3
- metadata +11 -11
- data/lib/use_cases/authorize.rb +0 -20
- data/lib/use_cases/base.rb +0 -8
- data/lib/use_cases/errors.rb +0 -9
- data/lib/use_cases/notifications.rb +0 -49
- data/lib/use_cases/prepare.rb +0 -19
- data/lib/use_cases/step_adapters/authorize.rb +0 -22
- data/lib/use_cases/transaction.rb +0 -25
- data/lib/use_cases/validate.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f810a00826d0e18fb1defbddb6a54055f4b77b43141a943d48d8a56273b7bf9
|
4
|
+
data.tar.gz: b6f1724c28186e1e8f4ca3b0b79807d4db534903b5f0ee5ace23363b180b8fc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8973b6cc8d4b89d16af39f010af3a8bf8cd9aea88f9c5c3679356955fc566ee1596711eed422beb9bd928364645640689ec03efd4e8cde11ec7b9d5c3e5c1357
|
7
|
+
data.tar.gz: b7c595a0645bf0b8938aa09f99d9fe8167b34c0ca6d9c6e9dfa4ed652a9c8341dfcf216fe70c222ad2321fb094f4667417a6bf1efb7cc1c8c2f2b79b8fe5c0a4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.0.1] - 2021-12-19
|
4
|
+
|
5
|
+
- Async published events are now suffixed by `".aync"`
|
6
|
+
|
7
|
+
## [1.0.0] - 2021-12-19
|
8
|
+
|
9
|
+
- Fixed some minor bugs that have been pending todos.
|
10
|
+
- Deprecated the `UseCases::Base` superclass as a DSL injection method. You must now use the `UseCase` module.
|
11
|
+
- Added the `publish` option for steps, which allows the publishing of an event when a step is completed.
|
12
|
+
|
3
13
|
## [0.1.0] - 2021-09-21
|
4
14
|
|
5
15
|
- Added the basic DSL of them with the following modules:
|
@@ -9,7 +19,7 @@
|
|
9
19
|
- Notifications [WIP]
|
10
20
|
- StackRunner
|
11
21
|
- Stack
|
12
|
-
-
|
22
|
+
- UseCases::Result
|
13
23
|
- Steps
|
14
24
|
- Validate
|
15
25
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -4,250 +4,179 @@
|
|
4
4
|
|
5
5
|
# UseCases
|
6
6
|
|
7
|
-
|
7
|
+
`UseCases` is a dry-ecosystem-based gem that implements a DSL for the use case pattern using the railway programming paradigm.
|
8
8
|
|
9
|
-
|
9
|
+
It's concept is largely based on `dry-transaction` but does not use it behind the scenes. Instead it relies on other `dry` libraries like [dry-validation](https://dry-rb.org/gems/dry-validation/), [dry-events](https://dry-rb.org/gems/dry-validation/) and [dry-monads](https://dry-rb.org/gems/dry-validation/) to implement a DSL that can be flexible enough for your needs.
|
10
10
|
|
11
|
-
|
11
|
+
### Including UseCase
|
12
12
|
|
13
|
-
|
13
|
+
Including the `UseCase` module ensures that your class implements the base use case [Base DSL](#the-base-dsl).
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
```ruby
|
16
|
+
class Users::Create
|
17
|
+
include UseCase
|
18
|
+
end
|
19
|
+
````
|
20
20
|
|
21
|
-
|
21
|
+
In order to add optional modules (optins), use the following notation:
|
22
22
|
|
23
23
|
```ruby
|
24
|
-
|
24
|
+
class Users::Create
|
25
|
+
include UseCase[:validated, :transactional, :publishing]
|
26
|
+
end
|
25
27
|
```
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
$ bundle install
|
30
|
-
|
31
|
-
Or install it yourself as:
|
32
|
-
|
33
|
-
$ gem install use_cases
|
34
|
-
|
35
|
-
## Usage
|
36
|
-
|
37
|
-
To fully understand `UseCases`, make sure to read [dry-transaction](https://dry-rb.org/gems/dry-transaction/0.13/)'s documentation first.
|
38
|
-
|
39
|
-
### Validations
|
40
|
-
|
41
|
-
See [dry-validation](https://dry-rb.org/gems/dry-validation/)
|
42
|
-
|
43
|
-
### Step Adapters
|
44
|
-
|
45
|
-
https://dry-rb.org/gems/dry-transaction/0.13/step-adapters/
|
46
|
-
|
47
|
-
**Basic Example**
|
29
|
+
### Using a UseCase
|
48
30
|
|
49
31
|
```ruby
|
50
|
-
|
51
|
-
|
32
|
+
create_user = Users::Create.new
|
33
|
+
params = { first_name: 'Don', last_name: 'Quixote' }
|
52
34
|
|
53
|
-
|
35
|
+
result = create_user.call(params, current_user)
|
54
36
|
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
end
|
37
|
+
# Checking if succeeded
|
38
|
+
result.success?
|
59
39
|
|
60
|
-
|
40
|
+
# Checking if failed
|
41
|
+
result.failure?
|
61
42
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
43
|
+
# Getting return value
|
44
|
+
result.value!
|
45
|
+
```
|
66
46
|
|
67
|
-
|
68
|
-
puts message
|
69
|
-
end
|
70
|
-
end
|
71
|
-
# => failed
|
47
|
+
Or with using dry-matcher by passing a block:
|
72
48
|
|
73
|
-
|
49
|
+
```ruby
|
50
|
+
create_user = Users::Create.new
|
51
|
+
params = { first_name: 'Don', last_name: 'Quixote' }
|
74
52
|
|
75
|
-
|
76
|
-
|
77
|
-
puts
|
53
|
+
create_user.call(params, current_user) do |on|
|
54
|
+
on.success do |user|
|
55
|
+
puts "#{user.first_name} created!"
|
78
56
|
end
|
79
57
|
|
80
|
-
|
81
|
-
puts message
|
58
|
+
on.failure do |(code, message)|
|
59
|
+
puts "Failure (#{code}): #{message}"
|
82
60
|
end
|
83
61
|
end
|
84
|
-
# => it succeeds!
|
85
62
|
```
|
86
63
|
|
87
|
-
|
88
|
-
|
89
|
-
```ruby
|
90
|
-
class YourCase < UseCases::Base
|
91
|
-
params {}
|
64
|
+
### Available Optins
|
92
65
|
|
93
|
-
try :load_some_resource
|
94
66
|
|
95
|
-
|
67
|
+
| Optin | Description |
|
68
|
+
|---|---|
|
69
|
+
| `:authorized` | Adds an extra `authorize` step macro, used to check user permissions. |
|
70
|
+
| `:prepared` | Adds an extra `prepare` step macro, used to run some code before the use case runs. |
|
71
|
+
| `:publishing` | Adds extra extra `publish` option to all steps, which allows a step to broadcast an event after executing |
|
72
|
+
| `:transactional` | Calls `#transaction` on a given `transaction_handler` object around the use case execution |
|
73
|
+
| `:validated` | Adds all methods of `dry-transaction` to the use case DSL, which run validations on the received `params` object. |
|
96
74
|
|
97
|
-
|
75
|
+
### The base DSL
|
98
76
|
|
99
|
-
|
77
|
+
Use cases implements a DSL similar to dry-transaction, using the [Railway programming paradigm](https://fsharpforfunandprofit.com/rop/).
|
100
78
|
|
101
|
-
map :create_some_already_validated_resource
|
102
79
|
|
103
|
-
|
80
|
+
Each step macro has a different use case, and so a different subset of available options, different expectations in return values, and interaction with the following step.
|
104
81
|
|
105
|
-
|
106
|
-
|
107
|
-
def load_some_resource(_, params)
|
108
|
-
Resource.find(params[:id])
|
109
|
-
end
|
82
|
+
By taking a simple look at the definition of a use case, anyone should be able to understand the business rules it emcompasses. For that it is necessary to understand the following matrix.
|
110
83
|
|
111
|
-
def change_this_resource(resource, params)
|
112
|
-
resource.text = params[:new_text]
|
113
|
-
|
114
|
-
if resource.text == params[:new_text]
|
115
|
-
Success(resource)
|
116
|
-
else
|
117
|
-
Failure([:failed, "could not update resource"])
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def log_a_message(resource)
|
122
|
-
Logger.info('Resource updated')
|
123
|
-
end
|
124
84
|
|
125
|
-
|
126
|
-
|
127
|
-
|
85
|
+
| | Rationale for use | Accepted Options | Expected return | Passes return value |
|
86
|
+
|---|---|---|---|---|
|
87
|
+
| **step** | This step has some complexity, and it can fail or succeed. | `with`, `pass` | `Success`/ `Failure` | ✅ |
|
88
|
+
| **check** | This step checks sets some rules for the operation, usually verifying that domain models fulfil some conditions. | `with`, `pass`, `failure`, `failure_message` | `boolean` | ❌ |
|
89
|
+
| **map** | Nothing should go wrong within this step. If it does, it's an unexpected application error. | `with`, `pass` | `any` | ✅ |
|
90
|
+
| **try** | We expect that, in some cases, errors will occur, and the operation fails in that case. | `catch`, `with`, `pass`, `failure`, `failure_message` | `any` | ✅ |
|
91
|
+
| **tee** | We don't care if this step succeeds or fails, it's used for non essential side effects. | `with`, `pass` | `any` | ❌ |
|
128
92
|
|
129
|
-
|
130
|
-
new_resource = Resource.create(text: params[:text])
|
131
|
-
end
|
93
|
+
#### Optional steps
|
132
94
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
95
|
+
| | Rationale for use | Accepted Options | Expected return | Passes return value |
|
96
|
+
|---|---|---|---|---|
|
97
|
+
| **enqueue** *(requires ActiveJob defined) | The same as a `tee`, but executed later to perform non-essential expensive operations. | `with`, `pass`, and sidekiq options | `any` | ❌ |
|
98
|
+
| **authorize**<br> *(requires authorized) | Performs authorization on the current user, by running a `check` which, in case of failure, always returns an `unauthorized` failure. | `with`, `pass`, `failure_message` | `boolean` | ❌ |
|
99
|
+
| **prepare**<br> *(requires prepared) | Adds a `tee` step that always runs first. Used to mutate params if necessary. | `with`, `pass` | `any` | ❌ |
|
138
100
|
|
139
|
-
### Authorization
|
140
101
|
|
141
|
-
|
102
|
+
### Defining Steps
|
142
103
|
|
143
|
-
|
104
|
+
Defining a step can be done in the body of the use case.
|
144
105
|
|
145
106
|
```ruby
|
146
|
-
class
|
147
|
-
|
148
|
-
user.age >= 18
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
user = User.where('age = 15').first
|
107
|
+
class Users::DeleteAccount
|
108
|
+
include UseCases[:validated, :transactional, :publishing, :validated]
|
153
109
|
|
154
|
-
|
155
|
-
match.failure :unauthorized do |(code, result)|
|
156
|
-
puts code
|
157
|
-
end
|
158
|
-
end
|
159
|
-
# => User must be over 18
|
110
|
+
step :do_something, {}
|
160
111
|
```
|
161
112
|
|
162
|
-
|
163
|
-
|
164
|
-
For the case of creating posts within a thread.
|
165
|
-
|
166
|
-
**Specs**
|
167
|
-
|
168
|
-
- Only active users or the thread owner can post.
|
169
|
-
- The post must be between 25 and 150 characters.
|
170
|
-
- The post must be sanitized to remove any sensitive or explicit content.
|
171
|
-
- The post must be saved to the database in the end.
|
172
|
-
- In case any conditions are not met, an failure should be returned with it's own error code.
|
113
|
+
In real life, a simple use case would look something like:
|
173
114
|
|
174
115
|
```ruby
|
175
|
-
class
|
116
|
+
class Users::DeleteAccount
|
117
|
+
include UseCases[:validated, :transactional, :publishing, :validated]
|
176
118
|
|
177
119
|
params do
|
178
|
-
required(:
|
179
|
-
required(:thread_id).filled(:integer)
|
120
|
+
required(:id).filled(:str?)
|
180
121
|
end
|
181
122
|
|
182
|
-
|
123
|
+
authorize :user_owns_account?, failure_message: 'Cannot delete account'
|
124
|
+
try :load_account, catch: ActiveRecord::RecordNotFound, failure: :account_not_found, failure_message: 'Account not found'
|
125
|
+
map :delete_account, publish: :account_deleted
|
126
|
+
enqueue :send_farewell_email
|
183
127
|
|
184
|
-
|
185
|
-
user.active?
|
186
|
-
end
|
128
|
+
private
|
187
129
|
|
188
|
-
|
189
|
-
|
130
|
+
def user_owns_account?(_previous_step_input, params, current_user)
|
131
|
+
current_user.account_id == params[:id]
|
190
132
|
end
|
191
133
|
|
192
|
-
|
193
|
-
|
194
|
-
step :create_post
|
195
|
-
|
196
|
-
private
|
197
|
-
|
198
|
-
def load_post_thread(params, user)
|
199
|
-
Thread.find(params[:thread_id])
|
134
|
+
def load_account(_previous_step_input, params, _current_user)
|
135
|
+
Account.find_by!(user_id: params[:id])
|
200
136
|
end
|
201
137
|
|
202
|
-
def
|
203
|
-
|
138
|
+
def delete_account(account, _params, _current_user)
|
139
|
+
account.destroy!
|
204
140
|
end
|
205
141
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
142
|
+
# since this executed async, all args are serialized
|
143
|
+
def send_farewell_email(account_attrs, params, current_user_attrs)
|
144
|
+
user = User.find(params[:id])
|
145
|
+
UserMailer.farewell(user).deliver_now!
|
210
146
|
end
|
211
147
|
end
|
212
148
|
```
|
213
149
|
|
214
|
-
|
150
|
+
#### Available Options
|
151
|
+
|
152
|
+
| Name | Description | Expected Usage |
|
153
|
+
|---|---|---|
|
154
|
+
| `with` | Retrieves the callable object used to perform the step. |<em><br> Symbol: `send(options[:with])` <br> String: `UseCases.config.container[options[:with]]` <br> Class: `options[:with]`</em> |
|
155
|
+
| `pass` | An array of the arguments to pass to the object set by `with`. <br> _options: params, current_user & previous_step_result_ | Array\<Symbol> |
|
156
|
+
| `failure` | The code passed to the Failure object. | Symbol / String |
|
157
|
+
| `failure_message` | The string message passed to the Failure object. | Symbol / String |
|
158
|
+
| `catch` | Array of error classes to rescue from. | Array\<Exception>
|
159
|
+
|
160
|
+
## Installation
|
161
|
+
|
162
|
+
Add this line to your application's Gemfile:
|
215
163
|
|
216
164
|
```ruby
|
217
|
-
|
218
|
-
class PostsController < ApplicationController
|
219
|
-
def create
|
220
|
-
Posts::Create.new.call(params, current_user) do |match|
|
221
|
-
|
222
|
-
# in success, the return value is the Success payload of the last step (#create_post)
|
223
|
-
match.success do |post|
|
224
|
-
# result => <Post:>
|
225
|
-
end
|
226
|
-
|
227
|
-
# in case ::params or any other dry-validation fails.
|
228
|
-
match.failure :validation_error do |result|
|
229
|
-
# result => [:validation_error, ['validation_error', { thread_id: 'is missing' }]
|
230
|
-
end
|
231
|
-
|
232
|
-
# in case ::try raises an error (ActiveRecord::NotFound in this case)
|
233
|
-
match.failure :not_found do |result|
|
234
|
-
# result => [:not_found, ['not_found', 'Could not find thread with id='<params[:thread_id]>'']
|
235
|
-
end
|
236
|
-
|
237
|
-
# in case any of the ::authorize blocks returns false
|
238
|
-
match.failure :unauthorized do |result|
|
239
|
-
# result => [:unauthorized, ['unauthorized', 'User is not active']
|
240
|
-
end
|
241
|
-
|
242
|
-
# in case #create_post returns a Failure
|
243
|
-
match.failure :failed_to_save do |result|
|
244
|
-
# result => [:failed_to_save, ['failed_to_save', { user_id: 'some error' }]
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|
165
|
+
gem 'use_cases'
|
249
166
|
```
|
250
167
|
|
168
|
+
And then execute:
|
169
|
+
|
170
|
+
$ bundle install
|
171
|
+
|
172
|
+
Or install it yourself as:
|
173
|
+
|
174
|
+
$ gem install use_cases
|
175
|
+
|
176
|
+
## Usage
|
177
|
+
|
178
|
+
To get a good basis to get started on `UseCases`, make sure to read [dry-transaction](https://dry-rb.org/gems/dry-transaction/0.13/)'s documentation first.
|
179
|
+
|
251
180
|
## Development
|
252
181
|
|
253
182
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/use_case.rb
CHANGED
@@ -6,20 +6,16 @@ require "dry/monads/do"
|
|
6
6
|
require "dry/monads/do/all"
|
7
7
|
require "dry/matcher/result_matcher"
|
8
8
|
|
9
|
-
require "use_cases/authorize"
|
10
9
|
require "use_cases/dsl"
|
11
|
-
require "use_cases/errors"
|
12
|
-
require "use_cases/validate"
|
13
10
|
require "use_cases/stack"
|
14
11
|
require "use_cases/params"
|
15
12
|
require "use_cases/stack_runner"
|
16
|
-
require "use_cases/
|
17
|
-
require "use_cases/notifications"
|
18
|
-
require "use_cases/prepare"
|
13
|
+
require "use_cases/result"
|
19
14
|
require "use_cases/step_adapters"
|
20
15
|
require "use_cases/module_optins"
|
21
16
|
|
22
17
|
module UseCase
|
18
|
+
extend UseCases::DSL
|
23
19
|
extend UseCases::ModuleOptins
|
24
20
|
|
25
21
|
def self.included(base)
|
@@ -33,7 +29,6 @@ module UseCase
|
|
33
29
|
extend UseCases::ModuleOptins
|
34
30
|
|
35
31
|
include UseCases::StepAdapters
|
36
|
-
include UseCases::Notifications
|
37
32
|
end
|
38
33
|
end
|
39
34
|
|
@@ -41,7 +36,6 @@ module UseCase
|
|
41
36
|
|
42
37
|
def initialize(*)
|
43
38
|
@stack = UseCases::Stack.new(self.class.__steps__).bind(self)
|
44
|
-
# self.class.bind_step_subscriptions
|
45
39
|
end
|
46
40
|
|
47
41
|
def call(params, current_user = nil)
|
data/lib/use_cases/dsl.rb
CHANGED
@@ -17,19 +17,5 @@ module UseCases
|
|
17
17
|
def __steps__
|
18
18
|
@__steps__ ||= []
|
19
19
|
end
|
20
|
-
|
21
|
-
def subscribe(listeners)
|
22
|
-
@listeners = listeners
|
23
|
-
|
24
|
-
if listeners.is_a?(Hash)
|
25
|
-
listeners.each do |step_name, listener|
|
26
|
-
__steps__.detect { |step| step.name == step_name }.subscribe(listener)
|
27
|
-
end
|
28
|
-
else
|
29
|
-
__steps__.each do |step|
|
30
|
-
step.subscribe(listeners)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
20
|
end
|
35
21
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
return unless defined? ActiveJob
|
3
|
+
|
4
|
+
module UseCases
|
5
|
+
module Events
|
6
|
+
class PublishJob < ActiveJob::Base
|
7
|
+
def perform(publish_key, payload)
|
8
|
+
publish_key += ".async"
|
9
|
+
UseCases.publisher.register_and_publish_event(publish_key, payload)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "dry/events/publisher"
|
2
|
+
|
3
|
+
module UseCases
|
4
|
+
module Events
|
5
|
+
class Publisher
|
6
|
+
include Dry::Events::Publisher[:use_cases]
|
7
|
+
|
8
|
+
def self.register_and_publish_event(event_name, payload)
|
9
|
+
register_events(event_name)
|
10
|
+
|
11
|
+
new.tap do |publisher|
|
12
|
+
publisher.subscribe_to_event(event_name)
|
13
|
+
publisher.publish(event_name, payload)
|
14
|
+
end
|
15
|
+
|
16
|
+
return unless defined? UseCases::Events::PublishJob
|
17
|
+
|
18
|
+
UseCases::Events::PublishJob.perform_later(event_name, payload)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.extract_payload(use_case_args)
|
22
|
+
{
|
23
|
+
return_value: use_case_args[0],
|
24
|
+
params: use_case_args[1],
|
25
|
+
current_user: use_case_args[2]
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.register_events(event_name)
|
30
|
+
[event_name, "#{event_name}.aync"].each do |key|
|
31
|
+
register_event(key) unless events[key]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def subscribe_to_event(event_name)
|
36
|
+
subscribers_for(event_name).each(&method(:subscribe))
|
37
|
+
end
|
38
|
+
|
39
|
+
def subscribers_for(event_name)
|
40
|
+
available_subscribers.select do |subscriber|
|
41
|
+
subscriber.subscribed_to?(event_name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def available_subscribers
|
46
|
+
UseCases::Events::Subscriber.descendants.map(&:new) + UseCases.subscribers
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "active_support/core_ext/class/subclasses"
|
2
|
+
|
3
|
+
module UseCases
|
4
|
+
module Events
|
5
|
+
class Subscriber
|
6
|
+
def subscribed_to?(event_name)
|
7
|
+
event_handler_name = "on_#{event_name.gsub('.', '_')}"
|
8
|
+
|
9
|
+
respond_to?(event_handler_name)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "use_cases/step_adapters/check"
|
4
|
+
|
5
|
+
module UseCases
|
6
|
+
module ModuleOptins
|
7
|
+
module Authorized
|
8
|
+
def self.included(base)
|
9
|
+
super
|
10
|
+
base.class_eval do
|
11
|
+
extend DSL
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module DSL
|
16
|
+
DEFAULT_OPTIONS = {
|
17
|
+
failure: :unauthorized,
|
18
|
+
failure_message: "Not Authorized",
|
19
|
+
merge_input_as: :resource
|
20
|
+
}
|
21
|
+
|
22
|
+
def authorize(name, options = {})
|
23
|
+
options = DEFAULT_OPTIONS.merge(options)
|
24
|
+
|
25
|
+
check name, options
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "use_cases/step_adapters/tee"
|
4
|
+
|
5
|
+
module UseCases
|
6
|
+
module ModuleOptins
|
7
|
+
module Prepared
|
8
|
+
def self.included(base)
|
9
|
+
super
|
10
|
+
base.class_eval do
|
11
|
+
extend DSL
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module DSL
|
16
|
+
def prepare(name, options = {})
|
17
|
+
__steps__.unshift StepAdapters::Tee.new(name, nil, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "use_cases/events/publisher"
|
4
|
+
require "use_cases/events/subscriber"
|
5
|
+
require "use_cases/events/publish_job"
|
6
|
+
|
7
|
+
module UseCases
|
8
|
+
module ModuleOptins
|
9
|
+
module Publishing
|
10
|
+
def self.included(base)
|
11
|
+
super
|
12
|
+
StepAdapters::Abstract.prepend StepCallPatch
|
13
|
+
end
|
14
|
+
|
15
|
+
module StepCallPatch
|
16
|
+
def call(*args)
|
17
|
+
super(*args).tap do |result|
|
18
|
+
publish_step_result(result, args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def publish_step_result(step_result, args)
|
23
|
+
return unless options[:publish]
|
24
|
+
|
25
|
+
key = extract_event_key(step_result)
|
26
|
+
payload = extract_payload(step_result, args)
|
27
|
+
|
28
|
+
UseCases.publisher.register_and_publish_event(key, payload)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def extract_payload(step_result, args)
|
34
|
+
{
|
35
|
+
return_value: step_result.value!,
|
36
|
+
params: args[-2],
|
37
|
+
current_user: args[-1]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_event_key(step_result)
|
42
|
+
options[:publish].to_s + (step_result.success? ? ".success" : ".failure")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UseCases
|
4
|
+
module ModuleOptins
|
5
|
+
module Transactional
|
6
|
+
class TransactionHandlerUndefined < StandardError; end
|
7
|
+
|
8
|
+
class TransactionHandlerInvalid < StandardError; end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
super
|
12
|
+
base.prepend DoCallPatch
|
13
|
+
end
|
14
|
+
|
15
|
+
module DoCallPatch
|
16
|
+
def do_call(*args)
|
17
|
+
unless respond_to?(:transaction_handler)
|
18
|
+
raise TransactionHandlerUndefined, "when using *transactional*, make sure to include a transaction handler in your dependencies."
|
19
|
+
end
|
20
|
+
|
21
|
+
raise TransactionHandlerInvalid, "Make sure your transaction_handler implements #transaction." unless transaction_handler.respond_to?(:transaction)
|
22
|
+
|
23
|
+
transaction_handler.transaction { super }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|