dry-transaction 0.11.2 → 0.13.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 +317 -0
- data/LICENSE +20 -0
- data/README.md +15 -43
- data/dry-transaction.gemspec +35 -0
- data/lib/dry-transaction.rb +2 -0
- data/lib/dry/transaction.rb +2 -0
- data/lib/dry/transaction/builder.rb +6 -4
- data/lib/dry/transaction/callable.rb +18 -0
- data/lib/dry/transaction/dsl.rb +9 -7
- data/lib/dry/transaction/errors.rb +2 -0
- data/lib/dry/transaction/instance_methods.rb +24 -20
- data/lib/dry/transaction/operation.rb +5 -3
- data/lib/dry/transaction/operation_resolver.rb +4 -2
- data/lib/dry/transaction/result_matcher.rb +7 -5
- data/lib/dry/transaction/stack.rb +2 -0
- data/lib/dry/transaction/step.rb +37 -24
- data/lib/dry/transaction/step_adapter.rb +6 -4
- data/lib/dry/transaction/step_adapters.rb +9 -7
- data/lib/dry/transaction/step_adapters/around.rb +4 -2
- data/lib/dry/transaction/step_adapters/check.rb +2 -0
- data/lib/dry/transaction/step_adapters/map.rb +2 -0
- data/lib/dry/transaction/step_adapters/raw.rb +5 -3
- data/lib/dry/transaction/step_adapters/tee.rb +2 -0
- data/lib/dry/transaction/step_adapters/try.rb +3 -1
- data/lib/dry/transaction/step_failure.rb +12 -0
- data/lib/dry/transaction/version.rb +3 -1
- metadata +14 -110
- data/Gemfile +0 -15
- data/Gemfile.lock +0 -97
- data/LICENSE.md +0 -9
- data/Rakefile +0 -6
- data/spec/examples.txt +0 -83
- data/spec/integration/around_spec.rb +0 -81
- data/spec/integration/auto_injection_spec.rb +0 -32
- data/spec/integration/custom_step_adapters_spec.rb +0 -41
- data/spec/integration/operation_spec.rb +0 -30
- data/spec/integration/passing_step_arguments_spec.rb +0 -51
- data/spec/integration/publishing_step_events_spec.rb +0 -119
- data/spec/integration/transaction_spec.rb +0 -566
- data/spec/integration/transaction_without_steps_spec.rb +0 -101
- data/spec/spec_helper.rb +0 -116
- data/spec/support/container.rb +0 -10
- data/spec/support/database.rb +0 -12
- data/spec/support/db_transactions.rb +0 -45
- data/spec/support/result_mixin.rb +0 -3
- data/spec/support/test_module_constants.rb +0 -11
- data/spec/unit/step_adapters/around_spec.rb +0 -46
- data/spec/unit/step_adapters/check_spec.rb +0 -43
- data/spec/unit/step_adapters/map_spec.rb +0 -16
- data/spec/unit/step_adapters/raw_spec.rb +0 -36
- data/spec/unit/step_adapters/tee_spec.rb +0 -17
- data/spec/unit/step_adapters/try_spec.rb +0 -89
- data/spec/unit/step_spec.rb +0 -131
data/lib/dry-transaction.rb
CHANGED
data/lib/dry/transaction.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/transaction/step'
|
4
|
+
require 'dry/transaction/dsl'
|
5
|
+
require 'dry/transaction/instance_methods'
|
6
|
+
require 'dry/transaction/operation_resolver'
|
5
7
|
|
6
8
|
module Dry
|
7
9
|
module Transaction
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Transaction
|
3
5
|
# @api private
|
@@ -29,10 +31,26 @@ module Dry
|
|
29
31
|
def call(*args, &block)
|
30
32
|
if arity.zero?
|
31
33
|
operation.(&block)
|
34
|
+
elsif ruby_27_last_arg_hash?(args)
|
35
|
+
*prefix, last = args
|
36
|
+
operation.(*prefix, **last, &block)
|
32
37
|
else
|
33
38
|
operation.(*args, &block)
|
34
39
|
end
|
35
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Ruby 2.7 gives a deprecation warning about passing a hash of parameters as the last argument
|
45
|
+
# to a method. Ruby 3.0 outright disallows it. This checks for that condition, but explicitly
|
46
|
+
# uses instance_of? rather than is_a? or kind_of?, because Hash like objects, specifically
|
47
|
+
# HashWithIndifferentAccess objects are provided by Rails as controller parameters, and often
|
48
|
+
# passed to dry-rb validators.
|
49
|
+
# In this case, it's better to leave the object as it's existing type, rather than implicitly
|
50
|
+
# convert it in to a hash with the double-splat (**) operator.
|
51
|
+
def ruby_27_last_arg_hash?(args)
|
52
|
+
args.last.instance_of?(Hash) && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
|
53
|
+
end
|
36
54
|
end
|
37
55
|
end
|
38
56
|
end
|
data/lib/dry/transaction/dsl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Transaction
|
3
5
|
class DSL < Module
|
@@ -24,16 +26,16 @@ module Dry
|
|
24
26
|
|
25
27
|
def define_dsl
|
26
28
|
module_exec(@step_adapters) do |step_adapters|
|
27
|
-
step_adapters.
|
29
|
+
step_adapters.each do |adapter_name, adapter|
|
28
30
|
define_method(adapter_name) do |step_name, with: nil, **options|
|
29
|
-
operation_name = with
|
31
|
+
operation_name = with
|
30
32
|
|
31
33
|
steps << Step.new(
|
32
|
-
|
33
|
-
step_name,
|
34
|
-
operation_name,
|
35
|
-
nil, # operations are resolved only when transactions are instantiated
|
36
|
-
options,
|
34
|
+
adapter: adapter,
|
35
|
+
name: step_name,
|
36
|
+
operation_name: operation_name,
|
37
|
+
operation: nil, # operations are resolved only when transactions are instantiated
|
38
|
+
options: options,
|
37
39
|
)
|
38
40
|
end
|
39
41
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/monads/result'
|
4
|
+
require 'dry/transaction/result_matcher'
|
5
|
+
require 'dry/transaction/stack'
|
4
6
|
|
5
7
|
module Dry
|
6
8
|
module Transaction
|
@@ -14,7 +16,7 @@ module Dry
|
|
14
16
|
|
15
17
|
def initialize(steps: (self.class.steps), listeners: nil, **operations)
|
16
18
|
@steps = steps.map { |step|
|
17
|
-
operation = resolve_operation(step, operations)
|
19
|
+
operation = resolve_operation(step, **operations)
|
18
20
|
step.with(operation: operation)
|
19
21
|
}
|
20
22
|
@operations = operations
|
@@ -42,7 +44,7 @@ module Dry
|
|
42
44
|
|
43
45
|
if listeners.is_a?(Hash)
|
44
46
|
listeners.each do |step_name, listener|
|
45
|
-
steps.detect { |step| step.
|
47
|
+
steps.detect { |step| step.name == step_name }.subscribe(listener)
|
46
48
|
end
|
47
49
|
else
|
48
50
|
steps.each do |step|
|
@@ -55,8 +57,8 @@ module Dry
|
|
55
57
|
assert_valid_step_args(step_args)
|
56
58
|
|
57
59
|
new_steps = steps.map { |step|
|
58
|
-
if step_args[step.
|
59
|
-
step.with(call_args: step_args[step.
|
60
|
+
if step_args[step.name]
|
61
|
+
step.with(call_args: step_args[step.name])
|
60
62
|
else
|
61
63
|
step
|
62
64
|
end
|
@@ -68,34 +70,36 @@ module Dry
|
|
68
70
|
private
|
69
71
|
|
70
72
|
def respond_to_missing?(name, _include_private = false)
|
71
|
-
steps.any? { |step| step.
|
73
|
+
steps.any? { |step| step.name == name }
|
72
74
|
end
|
73
75
|
|
74
76
|
def method_missing(name, *args, &block)
|
75
|
-
step = steps.detect { |s| s.
|
77
|
+
step = steps.detect { |s| s.name == name }
|
76
78
|
super unless step
|
77
79
|
|
78
|
-
operation = operations[step.
|
79
|
-
raise NotImplementedError, "no operation +#{step.operation_name}+ defined for step +#{step.
|
80
|
+
operation = operations[step.name]
|
81
|
+
raise NotImplementedError, "no operation +#{step.operation_name}+ defined for step +#{step.name}+" unless operation
|
80
82
|
|
81
83
|
operation.(*args, &block)
|
82
84
|
end
|
83
85
|
|
84
86
|
def resolve_operation(step, **operations)
|
85
|
-
if
|
86
|
-
|
87
|
-
elsif
|
88
|
-
|
89
|
-
elsif operations[step.
|
90
|
-
operations[step.
|
87
|
+
if step.internal? && operations[step.name]
|
88
|
+
operations[step.name]
|
89
|
+
elsif methods.include?(step.name) || private_methods.include?(step.name)
|
90
|
+
method(step.name)
|
91
|
+
elsif operations[step.name].respond_to?(:call)
|
92
|
+
operations[step.name]
|
93
|
+
elsif operations[step.name]
|
94
|
+
raise InvalidStepError.new(step.name)
|
91
95
|
else
|
92
|
-
raise
|
96
|
+
raise MissingStepError.new(step.name)
|
93
97
|
end
|
94
98
|
end
|
95
99
|
|
96
100
|
def assert_valid_step_args(step_args)
|
97
101
|
step_args.each_key do |step_name|
|
98
|
-
unless steps.any? { |step| step.
|
102
|
+
unless steps.any? { |step| step.name == step_name }
|
99
103
|
raise ArgumentError, "+#{step_name}+ is not a valid step name"
|
100
104
|
end
|
101
105
|
end
|
@@ -107,7 +111,7 @@ module Dry
|
|
107
111
|
num_args_supplied = step.call_args.length + 1 # add 1 for main `input`
|
108
112
|
|
109
113
|
if num_args_required > num_args_supplied
|
110
|
-
raise ArgumentError, "not enough arguments supplied for step +#{step.
|
114
|
+
raise ArgumentError, "not enough arguments supplied for step +#{step.name}+"
|
111
115
|
end
|
112
116
|
end
|
113
117
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Transaction
|
3
5
|
class OperationResolver < Module
|
@@ -5,7 +7,7 @@ module Dry
|
|
5
7
|
module_exec(container) do |ops_container|
|
6
8
|
define_method :initialize do |**kwargs|
|
7
9
|
operation_kwargs = self.class.steps.select(&:operation_name).map { |step|
|
8
|
-
operation = kwargs.fetch(step.
|
10
|
+
operation = kwargs.fetch(step.name) {
|
9
11
|
if ops_container && ops_container.key?(step.operation_name)
|
10
12
|
ops_container[step.operation_name]
|
11
13
|
else
|
@@ -13,7 +15,7 @@ module Dry
|
|
13
15
|
end
|
14
16
|
}
|
15
17
|
|
16
|
-
[step.
|
18
|
+
[step.name, operation]
|
17
19
|
}.to_h
|
18
20
|
|
19
21
|
super(**kwargs, **operation_kwargs)
|
@@ -1,21 +1,23 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/matcher'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Transaction
|
5
7
|
ResultMatcher = Dry::Matcher.new(
|
6
8
|
success: Dry::Matcher::Case.new(
|
7
|
-
match: -> result { result.
|
9
|
+
match: -> result { result.success? },
|
8
10
|
resolve: -> result { result.value! }
|
9
11
|
),
|
10
12
|
failure: Dry::Matcher::Case.new(
|
11
13
|
match: -> result, step_name = nil {
|
12
14
|
if step_name
|
13
|
-
result.
|
15
|
+
result.failure? && result.failure.step.name == step_name
|
14
16
|
else
|
15
|
-
result.
|
17
|
+
result.failure?
|
16
18
|
end
|
17
19
|
},
|
18
|
-
resolve: -> result { result.
|
20
|
+
resolve: -> result { result.failure.value }
|
19
21
|
)
|
20
22
|
)
|
21
23
|
end
|
data/lib/dry/transaction/step.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/monads/result'
|
2
4
|
require 'dry/events/publisher'
|
3
|
-
require
|
4
|
-
require
|
5
|
+
require 'dry/transaction/step_failure'
|
6
|
+
require 'dry/transaction/step_adapter'
|
5
7
|
|
6
8
|
module Dry
|
7
9
|
module Transaction
|
@@ -17,14 +19,14 @@ module Dry
|
|
17
19
|
register_event(:step_succeeded)
|
18
20
|
register_event(:step_failed)
|
19
21
|
|
20
|
-
attr_reader :
|
21
|
-
attr_reader :
|
22
|
+
attr_reader :adapter
|
23
|
+
attr_reader :name
|
22
24
|
attr_reader :operation_name
|
23
25
|
attr_reader :call_args
|
24
26
|
|
25
|
-
def initialize(
|
26
|
-
@
|
27
|
-
@
|
27
|
+
def initialize(adapter:, name:, operation_name:, operation: nil, options:, call_args: [])
|
28
|
+
@adapter = StepAdapter[adapter, operation, { **options, step_name: name, operation_name: operation_name }]
|
29
|
+
@name = name
|
28
30
|
@operation_name = operation_name
|
29
31
|
@call_args = call_args
|
30
32
|
end
|
@@ -32,47 +34,58 @@ module Dry
|
|
32
34
|
def with(operation: UNDEFINED, call_args: UNDEFINED)
|
33
35
|
return self if operation == UNDEFINED && call_args == UNDEFINED
|
34
36
|
|
35
|
-
new_operation = operation == UNDEFINED ?
|
37
|
+
new_operation = operation == UNDEFINED ? adapter.operation : operation
|
36
38
|
new_call_args = call_args == UNDEFINED ? self.call_args : Array(call_args)
|
37
39
|
|
38
40
|
self.class.new(
|
39
|
-
|
40
|
-
|
41
|
-
operation_name,
|
42
|
-
new_operation,
|
43
|
-
|
44
|
-
new_call_args
|
41
|
+
adapter: adapter,
|
42
|
+
name: name,
|
43
|
+
operation_name: operation_name,
|
44
|
+
operation: new_operation,
|
45
|
+
options: adapter.options,
|
46
|
+
call_args: new_call_args,
|
45
47
|
)
|
46
48
|
end
|
47
49
|
|
48
50
|
def call(input, continue = RETURN)
|
49
51
|
args = [input, *call_args]
|
50
52
|
|
51
|
-
if
|
52
|
-
with_broadcast(args) {
|
53
|
+
if adapter.yields?
|
54
|
+
with_broadcast(args) { adapter.(args, &continue) }
|
53
55
|
else
|
54
|
-
continue.(with_broadcast(args) {
|
56
|
+
continue.(with_broadcast(args) { adapter.(args) })
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
58
60
|
def with_broadcast(args)
|
59
|
-
publish(:step, step_name:
|
61
|
+
publish(:step, step_name: name, args: args)
|
60
62
|
|
61
63
|
yield.fmap { |value|
|
62
|
-
publish(:step_succeeded, step_name:
|
64
|
+
publish(:step_succeeded, step_name: name, args: args, value: value)
|
63
65
|
value
|
64
66
|
}.or { |value|
|
65
|
-
|
66
|
-
|
67
|
+
Failure(
|
68
|
+
StepFailure.(self, value) {
|
69
|
+
publish(:step_failed, step_name: name, args: args, value: value)
|
70
|
+
}
|
71
|
+
)
|
67
72
|
}
|
68
73
|
end
|
69
74
|
|
75
|
+
def internal?
|
76
|
+
!external?
|
77
|
+
end
|
78
|
+
|
79
|
+
def external?
|
80
|
+
!!operation_name
|
81
|
+
end
|
82
|
+
|
70
83
|
def arity
|
71
|
-
|
84
|
+
adapter.operation.arity
|
72
85
|
end
|
73
86
|
|
74
87
|
def operation
|
75
|
-
|
88
|
+
adapter.operation
|
76
89
|
end
|
77
90
|
end
|
78
91
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/transaction/callable'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Transaction
|
@@ -28,9 +30,9 @@ module Dry
|
|
28
30
|
|
29
31
|
@options = options
|
30
32
|
|
31
|
-
@yields = @adapter
|
32
|
-
|
33
|
-
|
33
|
+
@yields = @adapter
|
34
|
+
.parameters
|
35
|
+
.any? { |type, _| type == :block }
|
34
36
|
end
|
35
37
|
|
36
38
|
def yields?
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry-container'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Transaction
|
@@ -8,9 +10,9 @@ module Dry
|
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
13
|
+
require 'dry/transaction/step_adapters/check'
|
14
|
+
require 'dry/transaction/step_adapters/map'
|
15
|
+
require 'dry/transaction/step_adapters/raw'
|
16
|
+
require 'dry/transaction/step_adapters/tee'
|
17
|
+
require 'dry/transaction/step_adapters/try'
|
18
|
+
require 'dry/transaction/step_adapters/around'
|