use_cases 0.3.8 → 1.0.12
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 +11 -1
- data/Gemfile.lock +2 -2
- 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 +12 -12
- 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 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e41f6c5b8670e047cafdd85d068a66aa0efac27700f2c179d5d893ee1593c466
|
4
|
+
data.tar.gz: 629cef02e18325528d6155a34356b80e48acaf31520ef45fddb4937e058c3ad2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b5fdb9fd7bd52211b9194abd49fa2247b9b814e26f763f25f0c9f3ee995dff527977a19e7192576565a563af19dbf3cfeccae833fbd74f4fba74fa4589f46a3
|
7
|
+
data.tar.gz: 7d51d14db15b08764a1bbf3dc6ccede0806085e532e42273e436f1c91029f911282c3da31495b978a7dc87d5ba569fa33bc077b1b15673d7df795c9bf53ed781
|
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 `".async"`
|
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
|