dry-transaction 0.9.0 → 0.10.0
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/Gemfile.lock +6 -6
- data/README.md +11 -7
- data/lib/dry/transaction.rb +35 -43
- data/lib/dry/transaction/builder.rb +26 -0
- data/lib/dry/transaction/dsl.rb +30 -38
- data/lib/dry/transaction/instance_methods.rb +95 -0
- data/lib/dry/transaction/operation.rb +16 -0
- data/lib/dry/transaction/operation_resolver.rb +19 -0
- data/lib/dry/transaction/result_matcher.rb +2 -2
- data/lib/dry/transaction/step.rb +19 -8
- data/lib/dry/transaction/step_adapters.rb +1 -1
- data/lib/dry/transaction/step_adapters/map.rb +1 -1
- data/lib/dry/transaction/step_adapters/raw.rb +1 -1
- data/lib/dry/transaction/step_adapters/tee.rb +1 -1
- data/lib/dry/transaction/step_adapters/try.rb +1 -1
- data/lib/dry/transaction/step_failure.rb +1 -3
- data/lib/dry/transaction/version.rb +2 -2
- data/spec/examples.txt +60 -75
- data/spec/integration/custom_step_adapters_spec.rb +12 -12
- data/spec/integration/operation_spec.rb +30 -0
- data/spec/integration/passing_step_arguments_spec.rb +14 -13
- data/spec/integration/publishing_step_events_spec.rb +14 -12
- data/spec/integration/transaction_spec.rb +140 -22
- data/spec/spec_helper.rb +6 -2
- data/spec/unit/step_spec.rb +29 -0
- metadata +10 -10
- data/lib/dry/transaction/api.rb +0 -301
- data/lib/dry/transaction/step_definition.rb +0 -23
- data/spec/integration/custom_matcher_spec.rb +0 -59
- data/spec/unit/step_definition_spec.rb +0 -32
- data/spec/unit/transaction_spec.rb +0 -209
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.
|
1
|
+
if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.4"
|
2
2
|
require "simplecov"
|
3
|
-
SimpleCov.start
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "/spec/"
|
5
|
+
end
|
4
6
|
end
|
5
7
|
|
6
8
|
begin
|
@@ -8,6 +10,8 @@ begin
|
|
8
10
|
rescue LoadError; end
|
9
11
|
|
10
12
|
require "dry-transaction"
|
13
|
+
require "dry-matcher"
|
14
|
+
require "dry-monads"
|
11
15
|
|
12
16
|
# Requires supporting ruby files with custom matchers and macros, etc, in
|
13
17
|
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
data/spec/unit/step_spec.rb
CHANGED
@@ -48,6 +48,35 @@ RSpec.describe Dry::Transaction::Step do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
describe "#with" do
|
52
|
+
let(:operation) { proc { |a, b| a + b } }
|
53
|
+
context "without arguments" do
|
54
|
+
it "returns itself" do
|
55
|
+
expect(step.with).to eq step
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with operation argument" do
|
60
|
+
it "returns new instance with only operation changed" do
|
61
|
+
new_operation = proc { |a,b| a * b }
|
62
|
+
new_step = step.with(operation: new_operation)
|
63
|
+
expect(new_step).to_not eq step
|
64
|
+
expect(new_step.operation_name).to eq step.operation_name
|
65
|
+
expect(new_step.operation).to_not eq step.operation
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "with call_args argument" do
|
70
|
+
let(:call_args) { [12] }
|
71
|
+
it "returns new instance with only call_args changed" do
|
72
|
+
new_step = step.with(call_args: call_args)
|
73
|
+
expect(new_step).to_not eq step
|
74
|
+
expect(new_step.operation_name).to eq step.operation_name
|
75
|
+
expect(new_step.call_args).to_not eq step.call_args
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
51
80
|
describe "#arity" do
|
52
81
|
subject { step.arity }
|
53
82
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-transaction
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Riley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-container
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 1.
|
75
|
+
version: '1.15'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 1.
|
82
|
+
version: '1.15'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rake
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,8 +150,11 @@ files:
|
|
150
150
|
- Rakefile
|
151
151
|
- lib/dry-transaction.rb
|
152
152
|
- lib/dry/transaction.rb
|
153
|
-
- lib/dry/transaction/
|
153
|
+
- lib/dry/transaction/builder.rb
|
154
154
|
- lib/dry/transaction/dsl.rb
|
155
|
+
- lib/dry/transaction/instance_methods.rb
|
156
|
+
- lib/dry/transaction/operation.rb
|
157
|
+
- lib/dry/transaction/operation_resolver.rb
|
155
158
|
- lib/dry/transaction/result_matcher.rb
|
156
159
|
- lib/dry/transaction/step.rb
|
157
160
|
- lib/dry/transaction/step_adapters.rb
|
@@ -159,12 +162,11 @@ files:
|
|
159
162
|
- lib/dry/transaction/step_adapters/raw.rb
|
160
163
|
- lib/dry/transaction/step_adapters/tee.rb
|
161
164
|
- lib/dry/transaction/step_adapters/try.rb
|
162
|
-
- lib/dry/transaction/step_definition.rb
|
163
165
|
- lib/dry/transaction/step_failure.rb
|
164
166
|
- lib/dry/transaction/version.rb
|
165
167
|
- spec/examples.txt
|
166
|
-
- spec/integration/custom_matcher_spec.rb
|
167
168
|
- spec/integration/custom_step_adapters_spec.rb
|
169
|
+
- spec/integration/operation_spec.rb
|
168
170
|
- spec/integration/passing_step_arguments_spec.rb
|
169
171
|
- spec/integration/publishing_step_events_spec.rb
|
170
172
|
- spec/integration/transaction_spec.rb
|
@@ -175,9 +177,7 @@ files:
|
|
175
177
|
- spec/unit/step_adapters/raw_spec.rb
|
176
178
|
- spec/unit/step_adapters/tee_spec.rb
|
177
179
|
- spec/unit/step_adapters/try_spec.rb
|
178
|
-
- spec/unit/step_definition_spec.rb
|
179
180
|
- spec/unit/step_spec.rb
|
180
|
-
- spec/unit/transaction_spec.rb
|
181
181
|
homepage: https://github.com/dry-rb/dry-transaction
|
182
182
|
licenses:
|
183
183
|
- MIT
|
@@ -198,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
198
|
version: '0'
|
199
199
|
requirements: []
|
200
200
|
rubyforge_project:
|
201
|
-
rubygems_version: 2.
|
201
|
+
rubygems_version: 2.6.11
|
202
202
|
signing_key:
|
203
203
|
specification_version: 4
|
204
204
|
summary: Business Transaction Flow DSL
|
data/lib/dry/transaction/api.rb
DELETED
@@ -1,301 +0,0 @@
|
|
1
|
-
require "dry/monads/either"
|
2
|
-
|
3
|
-
module Dry
|
4
|
-
class Transaction
|
5
|
-
module API
|
6
|
-
# Run the transaction.
|
7
|
-
#
|
8
|
-
# Each operation will be called in the order it was specified, with its
|
9
|
-
# output passed as input to the next operation. Operations will only be
|
10
|
-
# called if the previous step was a success.
|
11
|
-
#
|
12
|
-
# If any of the operations require extra arguments beyond the main input
|
13
|
-
# e.g. with a signature like `#call(something_else, input)`, then you
|
14
|
-
# must pass the extra arguments as arrays for each step in the options
|
15
|
-
# hash.
|
16
|
-
#
|
17
|
-
# @example Running a transaction
|
18
|
-
# my_transaction.call(some_input)
|
19
|
-
#
|
20
|
-
# @example Running a transaction with extra step arguments
|
21
|
-
# my_transaction.call(some_input, step_name: [extra_argument])
|
22
|
-
#
|
23
|
-
# The return value will be the output from the last operation, wrapped
|
24
|
-
# in a [dry-monads](dry-monads) `Either` object, a `Right` for a successful
|
25
|
-
# transaction or a `Left` for a failed transaction.
|
26
|
-
#
|
27
|
-
# [dry-monads]: https://rubygems.org/gems/dry-monads
|
28
|
-
#
|
29
|
-
# @param input
|
30
|
-
# @param options [Hash] extra step arguments
|
31
|
-
#
|
32
|
-
# @return [Right, Left] output from the final step
|
33
|
-
#
|
34
|
-
# @api public
|
35
|
-
def call(input, options = {}, &block)
|
36
|
-
assert_valid_options(options)
|
37
|
-
assert_options_satisfy_step_arity(options)
|
38
|
-
|
39
|
-
steps = steps_with_options_applied(options)
|
40
|
-
result = steps.inject(Dry::Monads.Right(input), :bind)
|
41
|
-
|
42
|
-
if block
|
43
|
-
matcher.(result, &block)
|
44
|
-
else
|
45
|
-
result.or { |step_failure|
|
46
|
-
# Unwrap the value from the StepFailure and return it directly
|
47
|
-
Dry::Monads.Left(step_failure.value)
|
48
|
-
}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
alias_method :[], :call
|
52
|
-
|
53
|
-
# Subscribe to notifications from steps.
|
54
|
-
#
|
55
|
-
# When each step completes, it will send a `[step_name]_success` or
|
56
|
-
# `[step_name]_failure` message to any subscribers.
|
57
|
-
#
|
58
|
-
# For example, if you had a step called `persist`, then it would send
|
59
|
-
# either `persist_success` or `persist_failure` messages to subscribers
|
60
|
-
# after the operation completes.
|
61
|
-
#
|
62
|
-
# Pass a single object to subscribe to notifications from all steps, or
|
63
|
-
# pass a hash with step names as keys to subscribe to notifications from
|
64
|
-
# specific steps.
|
65
|
-
#
|
66
|
-
# @example Subscribing to notifications from all steps
|
67
|
-
# my_transaction.subscribe(my_listener)
|
68
|
-
#
|
69
|
-
# @example Subscribing to notifications from specific steps
|
70
|
-
# my_transaction.subscribe(some_step: my_listener, another_step: another_listener)
|
71
|
-
#
|
72
|
-
# Notifications are implemented using the [Wisper](wisper) gem.
|
73
|
-
#
|
74
|
-
# [wisper]: https://rubygems.org/gems/wisper
|
75
|
-
#
|
76
|
-
# @param listeners [Object, Hash{Symbol => Object}] the listener object or
|
77
|
-
# hash of steps and listeners
|
78
|
-
#
|
79
|
-
# @api public
|
80
|
-
def subscribe(listeners)
|
81
|
-
if listeners.is_a?(Hash)
|
82
|
-
listeners.each do |step_name, listener|
|
83
|
-
steps.detect { |step| step.step_name == step_name }.subscribe(listener)
|
84
|
-
end
|
85
|
-
else
|
86
|
-
steps.each do |step|
|
87
|
-
step.subscribe(listeners)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
self
|
92
|
-
end
|
93
|
-
|
94
|
-
# Return a transaction with the steps from the provided transaction
|
95
|
-
# prepended onto the beginning of the steps in `self`.
|
96
|
-
#
|
97
|
-
# @example Prepend an existing transaction
|
98
|
-
# my_transaction = Dry.Transaction(container: container) do
|
99
|
-
# step :first
|
100
|
-
# step :second
|
101
|
-
# end
|
102
|
-
#
|
103
|
-
# other_transaction = Dry.Transaction(container: container) do
|
104
|
-
# step :another
|
105
|
-
# end
|
106
|
-
#
|
107
|
-
# my_transaction.prepend(other_transaction)
|
108
|
-
#
|
109
|
-
# @example Prepend a transaction defined inline
|
110
|
-
# my_transaction = Dry.Transaction(container: container) do
|
111
|
-
# step :first
|
112
|
-
# step :second
|
113
|
-
# end
|
114
|
-
#
|
115
|
-
# my_transaction.prepend(container: container) do
|
116
|
-
# step :another
|
117
|
-
# end
|
118
|
-
#
|
119
|
-
# @param other [Dry::Transaction] the transaction to prepend.
|
120
|
-
# Optional if you will define a transaction inline via a block.
|
121
|
-
# @param options [Hash] the options hash for defining a transaction inline
|
122
|
-
# via a block. Optional if the transaction is passed directly as
|
123
|
-
# `other`.
|
124
|
-
# @option options [#[]] :container the operations container
|
125
|
-
#
|
126
|
-
# @return [Dry::Transaction] the modified transaction object
|
127
|
-
#
|
128
|
-
# @api public
|
129
|
-
def prepend(other = nil, **options, &block)
|
130
|
-
other = accept_or_build_transaction(other, **options, &block)
|
131
|
-
|
132
|
-
self.class.new(other.steps + steps, matcher)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Return a transaction with the steps from the provided transaction
|
136
|
-
# appended onto the end of the steps in `self`.
|
137
|
-
#
|
138
|
-
# @example Append an existing transaction
|
139
|
-
# my_transaction = Dry.Transaction(container: container) do
|
140
|
-
# step :first
|
141
|
-
# step :second
|
142
|
-
# end
|
143
|
-
#
|
144
|
-
# other_transaction = Dry.Transaction(container: container) do
|
145
|
-
# step :another
|
146
|
-
# end
|
147
|
-
#
|
148
|
-
# my_transaction.append(other_transaction)
|
149
|
-
#
|
150
|
-
# @example Append a transaction defined inline
|
151
|
-
# my_transaction = Dry.Transaction(container: container) do
|
152
|
-
# step :first
|
153
|
-
# step :second
|
154
|
-
# end
|
155
|
-
#
|
156
|
-
# my_transaction.append(container: container) do
|
157
|
-
# step :another
|
158
|
-
# end
|
159
|
-
#
|
160
|
-
# @param other [Dry::Transaction] the transaction to append.
|
161
|
-
# Optional if you will define a transaction inline via a block.
|
162
|
-
# @param options [Hash] the options hash for defining a transaction inline
|
163
|
-
# via a block. Optional if the transaction is passed directly as
|
164
|
-
# `other`.
|
165
|
-
# @option options [#[]] :container the operations container
|
166
|
-
#
|
167
|
-
# @return [Dry::Transaction] the modified transaction object
|
168
|
-
#
|
169
|
-
# @api public
|
170
|
-
def append(other = nil, **options, &block)
|
171
|
-
other = accept_or_build_transaction(other, **options, &block)
|
172
|
-
|
173
|
-
self.class.new(steps + other.steps, matcher)
|
174
|
-
end
|
175
|
-
|
176
|
-
# Return a transaction with the steps from the provided transaction
|
177
|
-
# inserted into a specific place among the steps in `self`.
|
178
|
-
#
|
179
|
-
# Transactions can be inserted either before or after a named step.
|
180
|
-
#
|
181
|
-
# @example Insert an existing transaction (before a step)
|
182
|
-
# my_transaction = Dry.Transaction(container: container) do
|
183
|
-
# step :first
|
184
|
-
# step :second
|
185
|
-
# end
|
186
|
-
#
|
187
|
-
# other_transaction = Dry.Transaction(container: container) do
|
188
|
-
# step :another
|
189
|
-
# end
|
190
|
-
#
|
191
|
-
# my_transaction.insert(other_transaction, before: :second)
|
192
|
-
#
|
193
|
-
# @example Append a transaction defined inline (after a step)
|
194
|
-
# my_transaction = Dry.Transaction(container: container) do
|
195
|
-
# step :first
|
196
|
-
# step :second
|
197
|
-
# end
|
198
|
-
#
|
199
|
-
# my_transaction.insert(after: :first, container: container) do
|
200
|
-
# step :another
|
201
|
-
# end
|
202
|
-
#
|
203
|
-
# @param other [Dry::Transaction] the transaction to append.
|
204
|
-
# Optional if you will define a transaction inline via a block.
|
205
|
-
# @param before [Symbol] the name of the step before which the
|
206
|
-
# transaction should be inserted (provide either this or `after`)
|
207
|
-
# @param after [Symbol] the name of the step after which the transaction
|
208
|
-
# should be inserted (provide either this or `before`)
|
209
|
-
# @param options [Hash] the options hash for defining a transaction
|
210
|
-
# inline via a block. Optional if the transaction is passed directly
|
211
|
-
# as `other`.
|
212
|
-
# @option options [#[]] :container the operations container
|
213
|
-
#
|
214
|
-
# @return [Dry::Transaction] the modified transaction object
|
215
|
-
#
|
216
|
-
# @api public
|
217
|
-
def insert(other = nil, before: nil, after: nil, **options, &block)
|
218
|
-
insertion_step = before || after
|
219
|
-
match_insertion_step = proc { |step| step.step_name == insertion_step }
|
220
|
-
|
221
|
-
unless steps.any?(&match_insertion_step)
|
222
|
-
raise ArgumentError, "+#{insertion_step}+ is not a valid step name"
|
223
|
-
end
|
224
|
-
|
225
|
-
other = accept_or_build_transaction(other, **options, &block)
|
226
|
-
index = steps.index(&match_insertion_step) + (after ? 1 : 0)
|
227
|
-
|
228
|
-
self.class.new(steps.dup.insert(index, *other.steps), matcher)
|
229
|
-
end
|
230
|
-
|
231
|
-
# @overload remove(step, ...)
|
232
|
-
# Return a transaction with steps removed.
|
233
|
-
#
|
234
|
-
# @example
|
235
|
-
# my_transaction = Dry.Transaction(container: container) do
|
236
|
-
# step :first
|
237
|
-
# step :second
|
238
|
-
# step :third
|
239
|
-
# end
|
240
|
-
#
|
241
|
-
# my_transaction.remove(:first, :third)
|
242
|
-
#
|
243
|
-
# @param step [Symbol] the names of a step to remove
|
244
|
-
# @param ... [Symbol] more names of steps to remove
|
245
|
-
#
|
246
|
-
# @return [Dry::Transaction] the modified transaction object
|
247
|
-
#
|
248
|
-
# @api public
|
249
|
-
def remove(*steps_to_remove)
|
250
|
-
self.class.new(steps.reject { |step| steps_to_remove.include?(step.step_name) }, matcher)
|
251
|
-
end
|
252
|
-
|
253
|
-
private
|
254
|
-
|
255
|
-
# @param options [Hash] step arguments keyed by step name
|
256
|
-
def assert_valid_options(options)
|
257
|
-
options.each_key do |step_name|
|
258
|
-
unless steps.any? { |step| step.step_name == step_name }
|
259
|
-
raise ArgumentError, "+#{step_name}+ is not a valid step name"
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
# @param options [Hash] step arguments keyed by step name
|
265
|
-
def assert_options_satisfy_step_arity(options)
|
266
|
-
steps.each do |step|
|
267
|
-
args_required = step.arity >= 0 ? step.arity : ~step.arity
|
268
|
-
args_supplied = options.fetch(step.step_name, []).length + 1 # add 1 for main `input`
|
269
|
-
|
270
|
-
if args_required > args_supplied
|
271
|
-
raise ArgumentError, "not enough options for step +#{step.step_name}+"
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
# @param options [Hash] step arguments keyed by step name
|
277
|
-
def steps_with_options_applied(options)
|
278
|
-
steps.map { |step|
|
279
|
-
if (args = options[step.step_name])
|
280
|
-
step.with_call_args(*args)
|
281
|
-
else
|
282
|
-
step
|
283
|
-
end
|
284
|
-
}
|
285
|
-
end
|
286
|
-
|
287
|
-
def accept_or_build_transaction(other_transaction = nil, **options, &block)
|
288
|
-
unless other_transaction || block
|
289
|
-
raise ArgumentError, "a transaction must be provided or defined in a block"
|
290
|
-
end
|
291
|
-
|
292
|
-
if other_transaction
|
293
|
-
other_transaction
|
294
|
-
else
|
295
|
-
require "dry/transaction/dsl"
|
296
|
-
DSL.new(**options, &block).call
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module Dry
|
2
|
-
class Transaction
|
3
|
-
# @api private
|
4
|
-
class StepDefinition
|
5
|
-
include Dry::Monads::Either::Mixin
|
6
|
-
|
7
|
-
def initialize(container, &block)
|
8
|
-
@container = container
|
9
|
-
@block = block
|
10
|
-
freeze
|
11
|
-
end
|
12
|
-
|
13
|
-
def call(*args)
|
14
|
-
instance_exec(*args, &block)
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
attr_reader :block
|
20
|
-
attr_reader :container
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|