use_cases 0.3.8 → 1.0.4
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 +1 -1
- data/README.md +21 -12
- data/lib/use_case.rb +2 -8
- data/lib/use_cases/dsl.rb +0 -14
- data/lib/use_cases/events/publish_job.rb +19 -0
- data/lib/use_cases/events/publisher.rb +9 -0
- data/lib/use_cases/events/subscriber.rb +6 -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 +45 -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 +5 -14
- data/lib/use_cases/step_active_job_adapter.rb +1 -1
- data/lib/use_cases/step_adapters/abstract.rb +45 -39
- data/lib/use_cases/step_adapters.rb +1 -0
- data/lib/use_cases/version.rb +1 -1
- data/lib/use_cases.rb +29 -2
- 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 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c1ed9f7d86b9109062b207489f37e67f84b93e676d7e81b930f219abbd47224
|
4
|
+
data.tar.gz: 2b9234f7075cb8240836875845d5d61036caa9a3c7feeb72abf31d2dd5830f91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe3e66534633de66dff8811dad8a3d2c9f0f350d392db60a7e91989d11a861510e32937abc77f47ff8d74b9439a8c6bb321e18d655f655af0778bf676efcc28f
|
7
|
+
data.tar.gz: 4da7071b68b859462feda741591b9862330f9b7dbe5ad127a76ff6052ac13bf7622241620c17a3ef20ee52e6f8b16c67e44b398f01621643df646b6d94759e62
|
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,17 +4,17 @@
|
|
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
|
-
|
10
|
-
|
11
|
-
`UseCases` does not however use `dry-transaction` 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 our needs.
|
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.
|
12
10
|
|
13
11
|
## Why `UseCases` came about:
|
14
12
|
|
15
13
|
1. It allows you to use `dry-validation` without much gymastics.
|
16
14
|
2. It abstracts common steps like **authorization** and **validation** into macros.
|
17
15
|
3. It solves what we consider a problem of `dry-transaction`. The way it funnels down `input` through `Dry::Monads` payloads alone. `UseCases` offers more flexibility in a way that still promotes functional programming values.
|
16
|
+
4. It implements a simple pub/sub mechanism which can be async when `ActiveJob` is a project dependency.
|
17
|
+
5. It implements an `enqueue` mechanism to delay execution of steps, also using `ActiveJob` as a dependency.
|
18
18
|
|
19
19
|
## Installation
|
20
20
|
|
@@ -34,25 +34,34 @@ Or install it yourself as:
|
|
34
34
|
|
35
35
|
## Usage
|
36
36
|
|
37
|
-
To
|
37
|
+
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.
|
38
38
|
|
39
39
|
### Validations
|
40
40
|
|
41
41
|
See [dry-validation](https://dry-rb.org/gems/dry-validation/)
|
42
42
|
|
43
|
-
###
|
44
|
-
|
45
|
-
https://dry-rb.org/gems/dry-transaction/0.13/step-adapters/
|
43
|
+
### Creating a Use Case
|
46
44
|
|
47
45
|
**Basic Example**
|
48
46
|
|
49
47
|
```ruby
|
50
|
-
class
|
51
|
-
|
48
|
+
class DeleteUser
|
49
|
+
include UseCase
|
50
|
+
|
51
|
+
check :current_user_is_user?
|
52
|
+
step :build_user
|
53
|
+
map :persist_user
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def current_user_is_user?(params, current_user)
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_user(params, current_user)
|
61
|
+
end
|
52
62
|
|
53
|
-
step :do_something
|
54
63
|
|
55
|
-
def do_something(params, current_user)
|
64
|
+
def do_something(user, params, current_user)
|
56
65
|
params[:should_fail] ? Failure([:failed, "failed"]) : Success("it succeeds!")
|
57
66
|
end
|
58
67
|
end
|
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,19 @@
|
|
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.publish(publish_key, payload)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def publisher
|
15
|
+
UseCases.publisher
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
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,45 @@
|
|
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
|
+
base.prepend DoCallPatch
|
13
|
+
end
|
14
|
+
|
15
|
+
module DoCallPatch
|
16
|
+
def do_call(*args)
|
17
|
+
super(*args, method(:publish_step_result).to_proc)
|
18
|
+
end
|
19
|
+
|
20
|
+
def publish_step_result(step_result, step_object)
|
21
|
+
publish_key = step_object.options[:publish]
|
22
|
+
publish_key += step_result.success? ? ".success" : ".failure"
|
23
|
+
payload = step_result.value!
|
24
|
+
return unless publish_key
|
25
|
+
|
26
|
+
register_event(publish_key)
|
27
|
+
peform_publish(publish_key, payload)
|
28
|
+
end
|
29
|
+
|
30
|
+
def register_event(publish_key)
|
31
|
+
return if UseCases.publisher.class.events[publish_key]
|
32
|
+
|
33
|
+
UseCases.publisher.class.register_event(publish_key)
|
34
|
+
end
|
35
|
+
|
36
|
+
def peform_publish(publish_key, payload)
|
37
|
+
UseCases.publisher.publish(publish_key, payload)
|
38
|
+
return unless defined? UseCases::Events::PublishJob
|
39
|
+
|
40
|
+
UseCases::Events::PublishJob.perform_later(publish_key, payload.to_json)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
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
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/validation/contract"
|
4
|
+
|
5
|
+
module UseCases
|
6
|
+
module ModuleOptins
|
7
|
+
module Validated
|
8
|
+
class NoValidationError < StandardError; end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
super
|
12
|
+
base.class_eval do
|
13
|
+
extend DSL
|
14
|
+
extend ClassMethods
|
15
|
+
prepend CallPatch
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module CallPatch
|
20
|
+
def call(*args)
|
21
|
+
unless stack.include_step?(:validate)
|
22
|
+
raise NoValidationError,
|
23
|
+
"Make sure to define params validations by using *params*" \
|
24
|
+
"*schema*, *json*, *rule* or *option* macros in your use case."
|
25
|
+
end
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module DSL
|
32
|
+
def params(*args, &blk)
|
33
|
+
_setup_validation
|
34
|
+
|
35
|
+
_contract_class.params(*args, &blk)
|
36
|
+
end
|
37
|
+
|
38
|
+
def schema(*args, &blk)
|
39
|
+
_setup_validation
|
40
|
+
|
41
|
+
_contract_class.schema(*args, &blk)
|
42
|
+
end
|
43
|
+
|
44
|
+
def rule(*args, &blk)
|
45
|
+
_setup_validation
|
46
|
+
|
47
|
+
_contract_class.rule(*args, &blk)
|
48
|
+
end
|
49
|
+
|
50
|
+
def json(*args, &blk)
|
51
|
+
_setup_validation
|
52
|
+
|
53
|
+
_contract_class.json(*args, &blk)
|
54
|
+
end
|
55
|
+
|
56
|
+
def option(*args, &blk)
|
57
|
+
_contract_class.option(*args, &blk)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def validate(params, *_args)
|
64
|
+
return Failure([:validation_error, "*params* must be a hash."]) unless params.respond_to?(:merge)
|
65
|
+
|
66
|
+
validation = contract.call(params)
|
67
|
+
|
68
|
+
if validation.success?
|
69
|
+
params.merge!(validation.to_h)
|
70
|
+
Success(validation.to_h)
|
71
|
+
else
|
72
|
+
Failure([:validation_error, validation.errors.to_h])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def contract
|
77
|
+
return self.class._contract_class.new if self.class._contract_class_defined?
|
78
|
+
end
|
79
|
+
|
80
|
+
module ClassMethods
|
81
|
+
def _setup_validation
|
82
|
+
_define_contract_class unless _contract_class_defined?
|
83
|
+
_define_validation_step unless _validation_step_defined?
|
84
|
+
end
|
85
|
+
|
86
|
+
def _define_validation_step
|
87
|
+
step :validate
|
88
|
+
end
|
89
|
+
|
90
|
+
def _contract_class
|
91
|
+
self::Contract
|
92
|
+
end
|
93
|
+
|
94
|
+
def _define_contract_class
|
95
|
+
const_set(:Contract, Class.new(Dry::Validation::Contract))
|
96
|
+
end
|
97
|
+
|
98
|
+
def _contract_class_name
|
99
|
+
"#{name}::Contract"
|
100
|
+
end
|
101
|
+
|
102
|
+
def _contract_class_defined?
|
103
|
+
Object.const_defined? _contract_class_name
|
104
|
+
end
|
105
|
+
|
106
|
+
def _validation_step_defined?
|
107
|
+
__steps__.map(&:name).include?(:validate)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -1,39 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "use_cases/
|
4
|
-
require "use_cases/
|
5
|
-
require "use_cases/
|
6
|
-
require "use_cases/
|
3
|
+
require "use_cases/module_optins/prepared"
|
4
|
+
require "use_cases/module_optins/transactional"
|
5
|
+
require "use_cases/module_optins/validated"
|
6
|
+
require "use_cases/module_optins/publishing"
|
7
|
+
require "use_cases/module_optins/authorized"
|
7
8
|
|
8
9
|
module UseCases
|
9
10
|
module ModuleOptins
|
10
11
|
attr_accessor :options
|
11
12
|
|
13
|
+
OPTINS = {
|
14
|
+
authorized: Authorized,
|
15
|
+
transactional: Transactional,
|
16
|
+
validated: Validated,
|
17
|
+
prepared: Prepared,
|
18
|
+
publishing: Publishing
|
19
|
+
}
|
20
|
+
|
12
21
|
def [](*options)
|
13
|
-
|
14
|
-
@modules << UseCases::Authorize if options.include?(:authorized)
|
15
|
-
@modules << UseCases::Transaction if options.include?(:transactional)
|
16
|
-
@modules << UseCases::Validate if options.include?(:validated)
|
17
|
-
@modules << UseCases::Prepare if options.include?(:prepared)
|
18
|
-
self
|
19
|
-
end
|
22
|
+
modules = [self]
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
return if @modules.empty?
|
24
|
+
OPTINS.each do |key, module_constant|
|
25
|
+
modules << module_constant if options.include?(key)
|
26
|
+
end
|
25
27
|
|
26
|
-
|
27
|
-
@modules = nil
|
28
|
-
end
|
28
|
+
supermodule = Module.new
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
return if @modules.empty?
|
30
|
+
supermodule.define_singleton_method(:included) do |base|
|
31
|
+
base.include(*modules)
|
32
|
+
end
|
34
33
|
|
35
|
-
|
36
|
-
@modules = nil
|
34
|
+
supermodule
|
37
35
|
end
|
38
36
|
|
39
37
|
def descendants
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module UseCases
|
4
|
-
class
|
4
|
+
class Result < Dry::Monads::Result
|
5
5
|
attr_reader :step, :result
|
6
6
|
|
7
7
|
def initialize(step, result)
|
@@ -14,7 +14,7 @@ module UseCases
|
|
14
14
|
return result if result_not_monad?
|
15
15
|
return nil if result_empty?
|
16
16
|
|
17
|
-
result.success? ? result.value! : result
|
17
|
+
result.success? ? result.value! : result.failure
|
18
18
|
end
|
19
19
|
alias value! value
|
20
20
|
|
@@ -23,25 +23,23 @@ module UseCases
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def failure?
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
def failure
|
30
|
-
failure? && value.failure
|
26
|
+
result.is_a?(Dry::Monads::Result::Failure)
|
31
27
|
end
|
32
28
|
|
33
29
|
def success
|
34
30
|
success? && value
|
35
31
|
end
|
36
32
|
|
33
|
+
def failure
|
34
|
+
failure? && value
|
35
|
+
end
|
36
|
+
|
37
37
|
def nil?
|
38
38
|
value.nil?
|
39
39
|
end
|
40
40
|
|
41
41
|
def to_result
|
42
|
-
|
43
|
-
|
44
|
-
result
|
42
|
+
self
|
45
43
|
end
|
46
44
|
|
47
45
|
def result_empty?
|
@@ -51,5 +49,13 @@ module UseCases
|
|
51
49
|
def result_not_monad?
|
52
50
|
!result.is_a?(Dry::Monads::Result)
|
53
51
|
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
if failure?
|
55
|
+
"UseCases::Result.Failure(#{value.inspect})"
|
56
|
+
else
|
57
|
+
"UseCases::Result.Success(#{value.inspect})"
|
58
|
+
end
|
59
|
+
end
|
54
60
|
end
|
55
61
|
end
|
@@ -38,13 +38,15 @@ RSpec::Matchers.define(:be_failure_with) do |*expected_failure|
|
|
38
38
|
expect(test_subject.failure).to eql expected_failure
|
39
39
|
end
|
40
40
|
|
41
|
+
expected_result, expected_code = expect_failure
|
42
|
+
|
41
43
|
failure_message do |test_subject|
|
42
44
|
if test_subject.failure?
|
43
|
-
"the use case was expected to fail with #{expected_result.inspect} but it returned #{test_subject.failure.inspect}"
|
45
|
+
"the use case was expected to fail with #{expected_code} and #{expected_result.inspect} but it returned #{test_subject.failure.inspect}"
|
44
46
|
else
|
45
47
|
"the use case was expected to fail but it succeeded with #{test_subject.success.inspect}"
|
46
48
|
end
|
47
|
-
end
|
49
|
+
end
|
48
50
|
end
|
49
51
|
|
50
52
|
RSpec::Matchers.define(:be_successful_with) do |expected_result|
|
@@ -59,5 +61,5 @@ RSpec::Matchers.define(:be_successful_with) do |expected_result|
|
|
59
61
|
else
|
60
62
|
"the use case was expected to succeed but it failed with #{test_subject.failure.inspect}"
|
61
63
|
end
|
62
|
-
end
|
64
|
+
end
|
63
65
|
end
|
@@ -8,9 +8,7 @@ module UseCases
|
|
8
8
|
@stack = stack
|
9
9
|
end
|
10
10
|
|
11
|
-
def call(*args
|
12
|
-
return around_block.call { do_call(*args) } if around_block
|
13
|
-
|
11
|
+
def call(*args)
|
14
12
|
do_call(*args)
|
15
13
|
end
|
16
14
|
|
@@ -19,6 +17,7 @@ module UseCases
|
|
19
17
|
def do_call(*args)
|
20
18
|
stack.call do
|
21
19
|
result = _run_step(stack, args)
|
20
|
+
args.last.call(result, stack.current_step) if args.last.is_a? Proc
|
22
21
|
|
23
22
|
return result if result.failure?
|
24
23
|
|
@@ -28,29 +27,21 @@ module UseCases
|
|
28
27
|
|
29
28
|
def _run_step(stack, args)
|
30
29
|
step = stack.current_step
|
31
|
-
|
32
|
-
step_args = _assert_step_arguments_with_count(stack, args)
|
30
|
+
step_args = _assert_step_arguments(stack, args)
|
33
31
|
|
34
32
|
raise MissingStepError, "Missing ##{step.name} implementation." if step.missing?
|
35
33
|
|
36
|
-
if expected_args_count != step_args.count
|
37
|
-
raise StepArgumentError,
|
38
|
-
"##{step.name} expects #{expected_args_count} arguments it only received #{step_args.count}, make sure your previous step Success() statement has a payload."
|
39
|
-
end
|
40
|
-
|
41
34
|
step.call(*step_args)
|
42
35
|
end
|
43
36
|
|
44
|
-
def
|
45
|
-
step_args_count = stack.current_step.args_count
|
46
|
-
|
37
|
+
def _assert_step_arguments(stack, args)
|
47
38
|
if _should_prepend_previous_step_result_to_args?(stack)
|
48
39
|
prev_step_result_value = stack.previous_step_value
|
49
40
|
|
50
41
|
args = [prev_step_result_value] + args
|
51
42
|
end
|
52
43
|
|
53
|
-
args
|
44
|
+
args
|
54
45
|
end
|
55
46
|
|
56
47
|
def _should_prepend_previous_step_result_to_args?(stack)
|
@@ -1,21 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry/monads/all"
|
4
|
+
require "byebug"
|
4
5
|
|
5
6
|
module UseCases
|
6
7
|
module StepAdapters
|
7
8
|
class Abstract
|
8
9
|
include Dry::Monads
|
9
|
-
|
10
|
-
# include Dry::Events::Publisher[name || object_id]
|
11
|
-
|
12
|
-
# def self.inherited(subclass)
|
13
|
-
# super
|
14
|
-
# subclass.register_event(:step)
|
15
|
-
# subclass.register_event(:step_succeeded)
|
16
|
-
# subclass.register_event(:step_failed)
|
17
|
-
# end
|
18
|
-
|
19
10
|
include Dry::Monads[:result]
|
20
11
|
|
21
12
|
attr_reader :name, :object, :failure, :options
|
@@ -31,22 +22,11 @@ module UseCases
|
|
31
22
|
end
|
32
23
|
|
33
24
|
def call(*args)
|
34
|
-
|
35
|
-
before_call(name, args: args)
|
36
|
-
|
37
|
-
result = StepResult.new(self, do_call(*args))
|
38
|
-
|
39
|
-
if result.success?
|
40
|
-
after_call_success(name, args: args, value: result.value)
|
41
|
-
else
|
42
|
-
after_call_failure(name, args: args, value: result.value)
|
43
|
-
end
|
44
|
-
|
45
|
-
result
|
46
|
-
end
|
25
|
+
UseCases::Result.new(self, do_call(*args))
|
47
26
|
end
|
48
27
|
|
49
|
-
def do_call(*
|
28
|
+
def do_call(*initial_args)
|
29
|
+
args = callable_args(initial_args)
|
50
30
|
callable_proc.call(*args)
|
51
31
|
end
|
52
32
|
|
@@ -54,45 +34,71 @@ module UseCases
|
|
54
34
|
self.class.new(name, object, options)
|
55
35
|
end
|
56
36
|
|
37
|
+
def callable_args(args)
|
38
|
+
if callable_args_count > args.count
|
39
|
+
raise StepArgumentError,
|
40
|
+
"##{step.name} expects #{expected_args_count} arguments it only received #{step_args.count}, make sure your previous step Success() statement has a payload."
|
41
|
+
end
|
42
|
+
|
43
|
+
return args.first(callable_args_count) unless external? && selects_external_args?
|
44
|
+
|
45
|
+
hashed_args(args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def hashed_args(args)
|
49
|
+
{
|
50
|
+
params: args.first,
|
51
|
+
current_user: args.last,
|
52
|
+
previous_step_result: args.last
|
53
|
+
}.slice(pass_option)
|
54
|
+
end
|
55
|
+
|
57
56
|
def callable_proc
|
58
57
|
callable_object.method(callable_method).to_proc
|
59
58
|
end
|
60
59
|
|
61
60
|
def callable_object
|
62
|
-
case
|
61
|
+
case with_option
|
63
62
|
when NilClass, FalseClass then object
|
64
|
-
when
|
65
|
-
|
63
|
+
when Symbol then object.send(with_option)
|
64
|
+
when String then UseCases.config.container[with_option]
|
65
|
+
else with_option
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
69
|
def callable_method
|
70
|
-
case
|
70
|
+
case with_option
|
71
71
|
when NilClass, FalseClass then name
|
72
72
|
else :call
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
options[:with]
|
76
|
+
def with_option
|
77
|
+
options[:with]
|
78
78
|
end
|
79
79
|
|
80
|
-
def
|
81
|
-
|
80
|
+
def pass_option
|
81
|
+
options[:pass]
|
82
82
|
end
|
83
83
|
|
84
|
-
def
|
85
|
-
|
84
|
+
def previous_input_param_name
|
85
|
+
options[:merge_input_as] || :input
|
86
86
|
end
|
87
87
|
|
88
|
-
def
|
88
|
+
def external?
|
89
|
+
with_option.present?
|
90
|
+
end
|
89
91
|
|
90
|
-
def
|
92
|
+
def selects_external_args?
|
93
|
+
pass_option.present?
|
94
|
+
end
|
91
95
|
|
92
|
-
def
|
96
|
+
def callable_args_count
|
97
|
+
callable_proc.parameters.count
|
98
|
+
end
|
93
99
|
|
94
|
-
def
|
95
|
-
|
100
|
+
def missing?
|
101
|
+
!callable_object.respond_to?(callable_method, true)
|
96
102
|
end
|
97
103
|
end
|
98
104
|
end
|
data/lib/use_cases/version.rb
CHANGED
data/lib/use_cases.rb
CHANGED
@@ -1,11 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dry/configurable"
|
4
|
+
|
3
5
|
require_relative "use_case"
|
4
6
|
require_relative "use_cases/version"
|
5
|
-
require_relative "use_cases/base"
|
6
7
|
|
7
8
|
module UseCases
|
8
|
-
|
9
|
+
class StepArgumentError < ArgumentError; end
|
10
|
+
|
11
|
+
class MissingStepError < NoMethodError; end
|
12
|
+
|
13
|
+
class PreviousStepInvalidReturn < StandardError; end
|
14
|
+
|
15
|
+
extend Dry::Configurable
|
16
|
+
|
17
|
+
setting :container, reader: true
|
18
|
+
setting :publisher, ::UseCases::Events::Publisher.new, reader: true
|
19
|
+
setting :subscribers, [], reader: true
|
20
|
+
|
21
|
+
def self.configure(&blk)
|
22
|
+
super
|
23
|
+
finalize_subscriptions!
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.finalize_subscriptions!
|
27
|
+
subscribe(*subscribers)
|
28
|
+
return unless UseCases::Events::Subscriber.respond_to?(:descendants)
|
29
|
+
|
30
|
+
usecase_subscriber_children = UseCases::Events::Subscriber.descendants
|
31
|
+
subscribe(*usecase_subscriber_children)
|
32
|
+
subscribers.concat(usecase_subscriber_children)
|
33
|
+
end
|
9
34
|
|
35
|
+
def self.subscribe(*subscribers)
|
36
|
+
subscribers.each { |subscriber| publisher.subscribe(subscriber) }
|
10
37
|
end
|
11
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: use_cases
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ring Twice
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -145,30 +145,30 @@ files:
|
|
145
145
|
- bin/setup
|
146
146
|
- lib/use_case.rb
|
147
147
|
- lib/use_cases.rb
|
148
|
-
- lib/use_cases/authorize.rb
|
149
|
-
- lib/use_cases/base.rb
|
150
148
|
- lib/use_cases/dsl.rb
|
151
|
-
- lib/use_cases/
|
149
|
+
- lib/use_cases/events/publish_job.rb
|
150
|
+
- lib/use_cases/events/publisher.rb
|
151
|
+
- lib/use_cases/events/subscriber.rb
|
152
152
|
- lib/use_cases/module_optins.rb
|
153
|
-
- lib/use_cases/
|
153
|
+
- lib/use_cases/module_optins/authorized.rb
|
154
|
+
- lib/use_cases/module_optins/prepared.rb
|
155
|
+
- lib/use_cases/module_optins/publishing.rb
|
156
|
+
- lib/use_cases/module_optins/transactional.rb
|
157
|
+
- lib/use_cases/module_optins/validated.rb
|
154
158
|
- lib/use_cases/params.rb
|
155
|
-
- lib/use_cases/
|
159
|
+
- lib/use_cases/result.rb
|
156
160
|
- lib/use_cases/rspec/matchers.rb
|
157
161
|
- lib/use_cases/stack.rb
|
158
162
|
- lib/use_cases/stack_runner.rb
|
159
163
|
- lib/use_cases/step_active_job_adapter.rb
|
160
164
|
- lib/use_cases/step_adapters.rb
|
161
165
|
- lib/use_cases/step_adapters/abstract.rb
|
162
|
-
- lib/use_cases/step_adapters/authorize.rb
|
163
166
|
- lib/use_cases/step_adapters/check.rb
|
164
167
|
- lib/use_cases/step_adapters/enqueue.rb
|
165
168
|
- lib/use_cases/step_adapters/map.rb
|
166
169
|
- lib/use_cases/step_adapters/step.rb
|
167
170
|
- lib/use_cases/step_adapters/tee.rb
|
168
171
|
- lib/use_cases/step_adapters/try.rb
|
169
|
-
- lib/use_cases/step_result.rb
|
170
|
-
- lib/use_cases/transaction.rb
|
171
|
-
- lib/use_cases/validate.rb
|
172
172
|
- lib/use_cases/version.rb
|
173
173
|
- use_cases.gemspec
|
174
174
|
homepage: https://github.com/listminut/use_cases
|
data/lib/use_cases/authorize.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module UseCases
|
4
|
-
module Authorize
|
5
|
-
class NoAuthorizationError < StandardError; end
|
6
|
-
|
7
|
-
def self.included(base)
|
8
|
-
base.class_eval do
|
9
|
-
extend DSL
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
module DSL
|
14
|
-
def authorize(step_name, options = {})
|
15
|
-
options[:failure] = :unauthorized
|
16
|
-
check step_name, **options
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
data/lib/use_cases/base.rb
DELETED
data/lib/use_cases/errors.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module UseCases
|
4
|
-
module Notifications
|
5
|
-
def self.included(base)
|
6
|
-
base.extend DSL
|
7
|
-
end
|
8
|
-
|
9
|
-
module DSL
|
10
|
-
def subscribe_to_step(event_id, listener)
|
11
|
-
step_subscriptions << StepSubscription.new(event_id, listener)
|
12
|
-
end
|
13
|
-
|
14
|
-
def bind_step_subscriptions
|
15
|
-
step_subscriptions.each { |subscription| subscription.bind(__steps__) }
|
16
|
-
end
|
17
|
-
|
18
|
-
def step_subscriptions
|
19
|
-
@step_subscriptions ||= []
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class StepSubscription
|
24
|
-
attr_reader :event_id, :listener, :steps
|
25
|
-
|
26
|
-
def initialize(event_id, listener)
|
27
|
-
@event_id = event_id
|
28
|
-
@listener = listener
|
29
|
-
end
|
30
|
-
|
31
|
-
def bind(steps)
|
32
|
-
@steps = steps
|
33
|
-
|
34
|
-
step = steps.find { |s| s.name == step_name }
|
35
|
-
step.subscribe(event_predicate, listener)
|
36
|
-
end
|
37
|
-
|
38
|
-
def event_predicate
|
39
|
-
predicate = event_id.to_s.gsub(step_name.to_s, "")
|
40
|
-
|
41
|
-
"step#{predicate}".to_sym
|
42
|
-
end
|
43
|
-
|
44
|
-
def step_name
|
45
|
-
steps.map(&:name).find { |step_name| step_name.start_with?(event_id.to_s) }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
data/lib/use_cases/prepare.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "use_cases/step_adapters/tee"
|
4
|
-
|
5
|
-
module UseCases
|
6
|
-
module Prepare
|
7
|
-
def self.included(base)
|
8
|
-
base.class_eval do
|
9
|
-
extend DSL
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
module DSL
|
14
|
-
def prepare(name, options = {})
|
15
|
-
__steps__.unshift StepAdapters::Tee.new(name, nil, options)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "use_cases/step_adapters/check"
|
4
|
-
|
5
|
-
module UseCases
|
6
|
-
module StepAdapters
|
7
|
-
class Authorize < UseCases::StepAdapters::Check
|
8
|
-
class InvalidReturnValue < StandardError; end
|
9
|
-
|
10
|
-
def do_call(*args)
|
11
|
-
result = super(*args)
|
12
|
-
prev_result = previous_step_result.value
|
13
|
-
raise InvalidReturnValue, "The return value should not be a Monad." if result.is_a?(Dry::Monads::Result)
|
14
|
-
|
15
|
-
failure_code = options[:failure] || :unauthorized
|
16
|
-
failure_message = options[:failure_message] || "Not Authorized"
|
17
|
-
|
18
|
-
result ? Success(prev_result) : Failure([failure_code, failure_message])
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module UseCases
|
4
|
-
module Transaction
|
5
|
-
class TransactionHandlerUndefined < StandardError; end
|
6
|
-
|
7
|
-
class TransactionHandlerInvalid < StandardError; end
|
8
|
-
|
9
|
-
def self.included(base)
|
10
|
-
base.prepend DoCallPatch
|
11
|
-
end
|
12
|
-
|
13
|
-
module DoCallPatch
|
14
|
-
def do_call(*args)
|
15
|
-
unless respond_to?(:transaction_handler)
|
16
|
-
raise TransactionHandlerUndefined, "when using *transactional*, make sure to include a transaction handler in your dependencies."
|
17
|
-
end
|
18
|
-
|
19
|
-
raise TransactionHandlerInvalid, "Make sure your transaction_handler implements #transaction." unless transaction_handler.respond_to?(:transaction)
|
20
|
-
|
21
|
-
transaction_handler.transaction { super }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/lib/use_cases/validate.rb
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "dry/validation/contract"
|
4
|
-
|
5
|
-
module UseCases
|
6
|
-
module Validate
|
7
|
-
class NoValidationError < StandardError; end
|
8
|
-
|
9
|
-
def self.included(base)
|
10
|
-
base.class_eval do
|
11
|
-
extend DSL
|
12
|
-
extend ClassMethods
|
13
|
-
prepend CallPatch
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
module CallPatch
|
18
|
-
def call(*args)
|
19
|
-
unless stack.include_step?(:validate)
|
20
|
-
raise NoValidationError,
|
21
|
-
"Make sure to define params validations by using *params*" \
|
22
|
-
"*schema*, *json*, *rule* or *option* macros in your use case."
|
23
|
-
end
|
24
|
-
|
25
|
-
super
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
module DSL
|
30
|
-
def params(*args, &blk)
|
31
|
-
_setup_validation
|
32
|
-
|
33
|
-
_contract_class.params(*args, &blk)
|
34
|
-
end
|
35
|
-
|
36
|
-
def schema(*args, &blk)
|
37
|
-
_setup_validation
|
38
|
-
|
39
|
-
_contract_class.schema(*args, &blk)
|
40
|
-
end
|
41
|
-
|
42
|
-
def rule(*args, &blk)
|
43
|
-
_setup_validation
|
44
|
-
|
45
|
-
_contract_class.rule(*args, &blk)
|
46
|
-
end
|
47
|
-
|
48
|
-
def json(*args, &blk)
|
49
|
-
_setup_validation
|
50
|
-
|
51
|
-
_contract_class.json(*args, &blk)
|
52
|
-
end
|
53
|
-
|
54
|
-
def option(*args, &blk)
|
55
|
-
_contract_class.option(*args, &blk)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def validate(params, current_user)
|
62
|
-
return Failure([:validation_error, "*params* must be a hash."]) unless params.respond_to?(:merge)
|
63
|
-
|
64
|
-
validation = contract.call(params)
|
65
|
-
|
66
|
-
if validation.success?
|
67
|
-
params.merge!(validation.to_h)
|
68
|
-
Success(validation.to_h)
|
69
|
-
else
|
70
|
-
Failure([:validation_error, validation.errors.to_h])
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def contract
|
75
|
-
return self.class._contract_class.new if self.class._contract_class_defined?
|
76
|
-
end
|
77
|
-
|
78
|
-
module ClassMethods
|
79
|
-
def _setup_validation
|
80
|
-
_define_contract_class unless _contract_class_defined?
|
81
|
-
_define_validation_step unless _validation_step_defined?
|
82
|
-
end
|
83
|
-
|
84
|
-
def _define_validation_step
|
85
|
-
step :validate
|
86
|
-
end
|
87
|
-
|
88
|
-
def _contract_class
|
89
|
-
self::Contract
|
90
|
-
end
|
91
|
-
|
92
|
-
def _define_contract_class
|
93
|
-
const_set(:Contract, Class.new(Dry::Validation::Contract))
|
94
|
-
end
|
95
|
-
|
96
|
-
def _contract_class_name
|
97
|
-
"#{name}::Contract"
|
98
|
-
end
|
99
|
-
|
100
|
-
def _contract_class_defined?
|
101
|
-
Object.const_defined? _contract_class_name
|
102
|
-
end
|
103
|
-
|
104
|
-
def _validation_step_defined?
|
105
|
-
__steps__.map(&:name).include?(:validate)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|