dry-transaction 0.5.0 → 0.6.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 +10 -0
- data/Gemfile.lock +23 -2
- data/README.md +4 -184
- data/lib/dry/transaction/dsl.rb +18 -20
- data/lib/dry/transaction/sequence.rb +1 -1
- data/lib/dry/transaction/step.rb +10 -4
- data/lib/dry/transaction/step_adapters.rb +8 -21
- data/lib/dry/transaction/step_adapters/map.rb +5 -5
- data/lib/dry/transaction/step_adapters/raw.rb +13 -5
- data/lib/dry/transaction/step_adapters/tee.rb +5 -5
- data/lib/dry/transaction/step_adapters/try.rb +9 -10
- data/lib/dry/transaction/version.rb +1 -1
- data/spec/examples.txt +39 -37
- data/spec/integration/custom_step_adapters_spec.rb +37 -0
- data/spec/integration/transaction_spec.rb +12 -0
- data/spec/spec_helper.rb +8 -4
- metadata +19 -5
- data/lib/dry/transaction/step_adapters/base.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1086edb5c79856eea4443feca03571381d01656
|
4
|
+
data.tar.gz: 396497b3dc1c00bac2b526d5dd9b2db3b94aec86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99f8334aabeda041d3d591affdde7030ddf9c227149a9f98230949adb076561fc903413bad9a51a634eeb26c6ccbe416bceb2d3a156dcf8a5d003b4d14df06e1
|
7
|
+
data.tar.gz: 1157a95e3126ea8108bbea2c795f1e094bcadd4ecddb26c1e62af2c1a9ebef7c32b181d9d67b3a62df0326f681c2ab7bd3448e302dcc42c43bfaa0c6a21e0a01
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,17 +1,33 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dry-transaction (0.
|
4
|
+
dry-transaction (0.6.0)
|
5
|
+
dry-container (>= 0.2.8)
|
5
6
|
kleisli
|
6
7
|
wisper (>= 1.6.0)
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
10
11
|
specs:
|
12
|
+
byebug (8.2.2)
|
13
|
+
codeclimate-test-reporter (0.5.0)
|
14
|
+
simplecov (>= 0.7.1, < 1.0.0)
|
15
|
+
coderay (1.1.1)
|
16
|
+
concurrent-ruby (1.0.1)
|
11
17
|
diff-lcs (1.2.5)
|
12
18
|
docile (1.1.5)
|
19
|
+
dry-configurable (0.1.4)
|
20
|
+
concurrent-ruby (~> 1.0)
|
21
|
+
dry-container (0.3.1)
|
22
|
+
concurrent-ruby (~> 1.0)
|
23
|
+
dry-configurable (~> 0.1, >= 0.1.3)
|
13
24
|
json (1.8.3)
|
14
25
|
kleisli (0.2.7)
|
26
|
+
method_source (0.8.2)
|
27
|
+
pry (0.10.3)
|
28
|
+
coderay (~> 1.1.0)
|
29
|
+
method_source (~> 0.8.1)
|
30
|
+
slop (~> 3.4)
|
15
31
|
rake (10.4.2)
|
16
32
|
rspec (3.3.0)
|
17
33
|
rspec-core (~> 3.3.0)
|
@@ -31,6 +47,7 @@ GEM
|
|
31
47
|
json (~> 1.8)
|
32
48
|
simplecov-html (~> 0.10.0)
|
33
49
|
simplecov-html (0.10.0)
|
50
|
+
slop (3.6.0)
|
34
51
|
wisper (1.6.1)
|
35
52
|
yard (0.8.7.6)
|
36
53
|
|
@@ -38,8 +55,12 @@ PLATFORMS
|
|
38
55
|
ruby
|
39
56
|
|
40
57
|
DEPENDENCIES
|
41
|
-
bundler (~> 1.
|
58
|
+
bundler (~> 1.11.2)
|
59
|
+
byebug
|
60
|
+
codeclimate-test-reporter
|
61
|
+
dry-container
|
42
62
|
dry-transaction!
|
63
|
+
pry
|
43
64
|
rake (~> 10.4.2)
|
44
65
|
rspec (~> 3.3.0)
|
45
66
|
simplecov (~> 0.10.0)
|
data/README.md
CHANGED
@@ -11,192 +11,12 @@
|
|
11
11
|
[][code_climate]
|
12
12
|
[][inch]
|
13
13
|
|
14
|
-
dry-transaction is a business transaction DSL. It provides a simple way to define a complex business transaction that includes processing by many different objects.
|
14
|
+
dry-transaction is a business transaction DSL. It provides a simple way to define a complex business transaction that includes processing by many different objects.
|
15
15
|
|
16
|
-
|
16
|
+
## Links
|
17
17
|
|
18
|
-
*
|
19
|
-
*
|
20
|
-
* A business transaction can describe its steps on an abstract level without being coupled to any details about how individual operations work.
|
21
|
-
* A business transaction doesn’t have any state.
|
22
|
-
* Each operation shouldn’t accumulate state, instead it should receive an input and return an output without causing any side-effects.
|
23
|
-
* The only interface of a an operation is `#call(input)`.
|
24
|
-
* Each operation provides a meaningful functionality and can be reused.
|
25
|
-
* Errors in any operation can be easily caught and handled as part of the normal application flow.
|
26
|
-
|
27
|
-
## Why?
|
28
|
-
|
29
|
-
Requiring a business transaction’s steps to exist as independent operations directly addressable via a container means that they can be tested in isolation and easily reused throughout your application. Following from this, keeping the business transaction to a series of high-level, declarative steps ensures that it’s easy to understand at a glance.
|
30
|
-
|
31
|
-
The output of each step is wrapped in a [Kleisli](https://github.com/txus/kleisli) `Either` object (`Right` for success or `Left` for failure). This allows the steps to be chained together and ensures that processing stops in the case of a failure. Returning an `Either` from the overall transaction also allows for error handling to remain a primary concern without it getting in the way of tidy, straightforward operation logic. Wrapping the step output also means that you can work with a wide variety of operations within your application – they don’t need to return an `Either` already.
|
32
|
-
|
33
|
-
## Usage
|
34
|
-
|
35
|
-
### Container
|
36
|
-
|
37
|
-
All you need to use dry-transaction is a container to hold your application’s operations. Each operation must respond to `#call(input)`.
|
38
|
-
|
39
|
-
The operations will be resolved from the container via `#[]`. For our examples, we’ll use a plain hash:
|
40
|
-
|
41
|
-
```ruby
|
42
|
-
container = {
|
43
|
-
process: -> input { {name: input["name"], email: input["email"]} },
|
44
|
-
validate: -> input { input[:email].nil? ? raise(ValidationFailure, "not valid") : input },
|
45
|
-
persist: -> input { DB << input and true }
|
46
|
-
}
|
47
|
-
```
|
48
|
-
|
49
|
-
For larger apps, you may like to consider something like [dry-container](https://github.com/dryrb/dry-container).
|
50
|
-
|
51
|
-
### Defining a transaction
|
52
|
-
|
53
|
-
Define a transaction to bring your opererations together:
|
54
|
-
|
55
|
-
```ruby
|
56
|
-
save_user = Dry.Transaction(container: container) do
|
57
|
-
map :process
|
58
|
-
try :validate, catch: ValidationFailure
|
59
|
-
tee :persist
|
60
|
-
end
|
61
|
-
```
|
62
|
-
|
63
|
-
Operations are formed into steps using _step adapters._ Step adapters wrap the output of your operations to make them easy to integrate into a transaction. The following adapters are available:
|
64
|
-
|
65
|
-
* `step` – the operation already returns an `Either` object (`Right(output)` for success and `Left(output)` for failure), and needs no special handling.
|
66
|
-
* `map` – any output is considered successful and returned as `Right(output)`
|
67
|
-
* `try` – the operation may raise an exception in an error case. This is caught and returned as `Left(exception)`. The output is otherwise returned as `Right(output)`.
|
68
|
-
* `tee` – the operation interacts with some external system and has no meaningful output. The original input is passed through and returned as `Right(input)`.
|
69
|
-
|
70
|
-
### Calling a transaction
|
71
|
-
|
72
|
-
Calling a transaction will run its operations in their specified order, with the output of each operation becoming the input for the next.
|
73
|
-
|
74
|
-
```ruby
|
75
|
-
DB = []
|
76
|
-
|
77
|
-
save_user.call("name" => "Jane", "email" => "jane@doe.com")
|
78
|
-
# => Right({:name=>"Jane", :email=>"jane@doe.com"})
|
79
|
-
|
80
|
-
DB
|
81
|
-
# => [{:name=>"Jane", :email=>"jane@doe.com"}]
|
82
|
-
```
|
83
|
-
|
84
|
-
Each transaction returns a result value wrapped in a `Left` or `Right` object (based on the output of its final step). You can handle these results (including errors arising from particular steps) with a match block:
|
85
|
-
|
86
|
-
```ruby
|
87
|
-
save_user.call(name: "Jane", email: "jane@doe.com") do |m|
|
88
|
-
m.success do |value|
|
89
|
-
puts "Succeeded!"
|
90
|
-
end
|
91
|
-
|
92
|
-
m.failure :validate do |error|
|
93
|
-
# In a more realistic example, you’d loop through a list of messages in `errors`.
|
94
|
-
puts "Please provide an email address."
|
95
|
-
end
|
96
|
-
|
97
|
-
m.failure do |error|
|
98
|
-
puts "Couldn’t save this user."
|
99
|
-
end
|
100
|
-
end
|
101
|
-
```
|
102
|
-
|
103
|
-
### Passing additional step arguments
|
104
|
-
|
105
|
-
Additional arguments for step operations can be passed at the time of calling your transaction. Provide these arguments as an array, and they’ll be [splatted](https://endofline.wordpress.com/2011/01/21/the-strange-ruby-splat/) into the front of the operation’s arguments. This means that transactions can effectively support operations with any sort of `#call(*args, input)` interface.
|
106
|
-
|
107
|
-
```ruby
|
108
|
-
DB = []
|
109
|
-
|
110
|
-
container = {
|
111
|
-
process: -> input { {name: input["name"], email: input["email"]} },
|
112
|
-
validate: -> allowed, input { input[:email].include?(allowed) ? raise(ValidationFailure, "not allowed") : input },
|
113
|
-
persist: -> input { DB << input and true }
|
114
|
-
}
|
115
|
-
|
116
|
-
save_user = Dry.Transaction(container: container) do
|
117
|
-
map :process
|
118
|
-
try :validate, catch: ValidationFailure
|
119
|
-
tee :persist
|
120
|
-
end
|
121
|
-
|
122
|
-
input = {"name" => "Jane", "email" => "jane@doe.com"}
|
123
|
-
save_user.call(input, validate: ["doe.com"])
|
124
|
-
# => Right({:name=>"Jane", :email=>"jane@doe.com"})
|
125
|
-
|
126
|
-
save_user.call(input, validate: ["smith.com"])
|
127
|
-
# => Left("not allowed")
|
128
|
-
```
|
129
|
-
|
130
|
-
### Subscribing to step notifications
|
131
|
-
|
132
|
-
As well as pattern matching on the final transaction result, you can subscribe to individual steps and trigger specific behaviour based on their success or failure:
|
133
|
-
|
134
|
-
```ruby
|
135
|
-
NOTIFICATIONS = []
|
136
|
-
|
137
|
-
module UserPersistListener
|
138
|
-
extend self
|
139
|
-
|
140
|
-
def persist_success(user)
|
141
|
-
NOTIFICATIONS << "#{user[:email]} persisted"
|
142
|
-
end
|
143
|
-
|
144
|
-
def persist_failure(user)
|
145
|
-
NOTIFICATIONS << "#{user[:email]} failed to persist"
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
|
150
|
-
input = {"name" => "Jane", "email" => "jane@doe.com"}
|
151
|
-
|
152
|
-
save_user.subscribe(persist: UserPersistListener)
|
153
|
-
save_user.call(input, validate: ["doe.com"])
|
154
|
-
|
155
|
-
NOTIFICATIONS
|
156
|
-
# => ["jane@doe.com persisted"]
|
157
|
-
```
|
158
|
-
|
159
|
-
This pub/sub mechanism is provided by the [Wisper](https://github.com/krisleech/wisper) gem. You can subscribe to specific steps using the `#subscribe(step_name: listener)` API, or subscribe to all steps via `#subscribe(listener)`.
|
160
|
-
|
161
|
-
### Extending transactions
|
162
|
-
|
163
|
-
You can extend existing transactions by inserting or removing steps. See the [API docs](http://www.rubydoc.info/github/dry-rb/dry-transaction/Dry/Transaction/Sequence) for more information.
|
164
|
-
|
165
|
-
### Working with a larger container
|
166
|
-
|
167
|
-
In practice, your container won’t be a trivial collection of generically named operations. You can keep your transaction step names simple by using the `with:` option to provide the identifiers for the operations within your container:
|
168
|
-
|
169
|
-
```ruby
|
170
|
-
save_user = Dry.Transaction(container: large_whole_app_container) do
|
171
|
-
map :process, with: "attributes.user"
|
172
|
-
try :validate, with: "validations.user", catch: ValidationFailure
|
173
|
-
tee :persist, with: "persistance.commands.update_user"
|
174
|
-
end
|
175
|
-
```
|
176
|
-
|
177
|
-
## Installation
|
178
|
-
|
179
|
-
Add this line to your application’s `Gemfile`:
|
180
|
-
|
181
|
-
```ruby
|
182
|
-
gem "dry-transaction"
|
183
|
-
```
|
184
|
-
|
185
|
-
Run `bundle` to install the gem.
|
186
|
-
|
187
|
-
## Documentation
|
188
|
-
|
189
|
-
View the [full API documentation](http://www.rubydoc.info/github/dry-rb/dry-transaction) on RubyDoc.info.
|
190
|
-
|
191
|
-
## Contributing
|
192
|
-
|
193
|
-
Bug reports and pull requests are welcome on [GitHub](http://github.com/dry-rb/dry-transaction).
|
194
|
-
|
195
|
-
## Credits
|
196
|
-
|
197
|
-
dry-transaction is developed and maintained by [Icelab](http://icelab.com.au/).
|
198
|
-
|
199
|
-
dry-transaction’s error handling is based on Scott Wlaschin’s [Railway Oriented Programming](http://fsharpforfunandprofit.com/rop/), found via Zohaib Rauf’s [Railway Oriented Programming in Elixir](http://zohaib.me/railway-programming-pattern-in-elixir/) blog post. dry-transaction’s behavior as a business transaction library draws heavy inspiration from Piotr Solnica’s [Transflow](http://github.com/solnic/transflow) and Gilbert B Garza’s [Solid Use Case](https://github.com/mindeavor/solid_use_case). Josep M. Bach’s [Kleisli](https://github.com/txus/kleisli) gem makes functional programming patterns in Ruby accessible and fun. Thank you all!
|
18
|
+
* [Documentation](http://dry-rb.org/gems/dry-transaction)
|
19
|
+
* [API documentation](http://www.rubydoc.info/github/dry-rb/dry-transaction)
|
200
20
|
|
201
21
|
## License
|
202
22
|
|
data/lib/dry/transaction/dsl.rb
CHANGED
@@ -1,41 +1,39 @@
|
|
1
1
|
require "dry/transaction/step"
|
2
2
|
require "dry/transaction/step_adapters"
|
3
|
-
require "dry/transaction/step_adapters/base"
|
4
|
-
require "dry/transaction/step_adapters/map"
|
5
|
-
require "dry/transaction/step_adapters/raw"
|
6
|
-
require "dry/transaction/step_adapters/tee"
|
7
|
-
require "dry/transaction/step_adapters/try"
|
8
3
|
require "dry/transaction/sequence"
|
9
4
|
|
10
5
|
module Dry
|
11
6
|
module Transaction
|
7
|
+
# @api private
|
12
8
|
class DSL
|
13
|
-
# @api private
|
14
|
-
attr_reader :options
|
15
|
-
|
16
|
-
# @api private
|
17
9
|
attr_reader :container
|
18
|
-
|
19
|
-
# @api private
|
10
|
+
attr_reader :step_adapters
|
20
11
|
attr_reader :steps
|
21
12
|
|
22
|
-
# @api private
|
23
13
|
def initialize(options, &block)
|
24
|
-
@options = options
|
25
14
|
@container = options.fetch(:container)
|
15
|
+
@step_adapters = options.fetch(:step_adapters, StepAdapters)
|
26
16
|
@steps = []
|
27
17
|
|
28
|
-
|
18
|
+
instance_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def respond_to_missing?(method_name)
|
22
|
+
step_adapters.key?(method_name)
|
29
23
|
end
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
def method_missing(method_name, *args)
|
26
|
+
return super unless step_adapters.key?(method_name)
|
27
|
+
|
28
|
+
step_adapter = step_adapters[method_name]
|
29
|
+
step_name = args.first
|
30
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
31
|
+
operation_name = options.delete(:with) || step_name
|
32
|
+
operation = container[operation_name]
|
33
|
+
|
34
|
+
steps << Step.new(step_adapter, step_name, operation_name, operation, options)
|
36
35
|
end
|
37
36
|
|
38
|
-
# @api private
|
39
37
|
def call
|
40
38
|
Sequence.new(steps)
|
41
39
|
end
|
@@ -72,7 +72,7 @@ module Dry
|
|
72
72
|
# my_transaction.subscribe(my_listener)
|
73
73
|
#
|
74
74
|
# @example Subscribing to notifications from specific steps
|
75
|
-
# my_transaction.
|
75
|
+
# my_transaction.subscribe(some_step: my_listener, another_step: another_listener)
|
76
76
|
#
|
77
77
|
# Notifications are implemented using the [Wisper](wisper) gem.
|
78
78
|
#
|
data/lib/dry/transaction/step.rb
CHANGED
@@ -7,23 +7,29 @@ module Dry
|
|
7
7
|
class Step
|
8
8
|
include Wisper::Publisher
|
9
9
|
|
10
|
+
attr_reader :step_adapter
|
10
11
|
attr_reader :step_name
|
12
|
+
attr_reader :operation_name
|
11
13
|
attr_reader :operation
|
14
|
+
attr_reader :options
|
12
15
|
attr_reader :call_args
|
13
16
|
|
14
|
-
def initialize(step_name, operation, call_args = [])
|
17
|
+
def initialize(step_adapter, step_name, operation_name, operation, options, call_args = [])
|
18
|
+
@step_adapter = step_adapter
|
15
19
|
@step_name = step_name
|
20
|
+
@operation_name = operation_name
|
16
21
|
@operation = operation
|
22
|
+
@options = options
|
17
23
|
@call_args = call_args
|
18
24
|
end
|
19
25
|
|
20
26
|
def with_call_args(*call_args)
|
21
|
-
self.class.new(step_name, operation, call_args)
|
27
|
+
self.class.new(step_adapter, step_name, operation_name, operation, options, call_args)
|
22
28
|
end
|
23
29
|
|
24
30
|
def call(input)
|
25
31
|
args = call_args + [input]
|
26
|
-
result =
|
32
|
+
result = step_adapter.call(self, *args)
|
27
33
|
|
28
34
|
result.fmap { |value|
|
29
35
|
broadcast :"#{step_name}_success", value
|
@@ -35,7 +41,7 @@ module Dry
|
|
35
41
|
end
|
36
42
|
|
37
43
|
def arity
|
38
|
-
operation.arity
|
44
|
+
operation.is_a?(Proc) ? operation.arity : operation.method(:call).arity
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
@@ -1,27 +1,14 @@
|
|
1
|
-
require "
|
1
|
+
require "dry-container"
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Transaction
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
class << self
|
9
|
-
attr_reader :registry
|
10
|
-
private :registry
|
11
|
-
|
12
|
-
extend Forwardable
|
13
|
-
def_delegators :registry, :[], :each
|
14
|
-
|
15
|
-
# Register a step adapter.
|
16
|
-
#
|
17
|
-
# @param [Symbol] name the name to expose for adding steps to a transaction
|
18
|
-
# @param klass the step adapter class
|
19
|
-
#
|
20
|
-
# @api public
|
21
|
-
def register(name, klass)
|
22
|
-
registry[name.to_sym] = klass
|
23
|
-
end
|
24
|
-
end
|
5
|
+
class StepAdapters
|
6
|
+
extend Dry::Container::Mixin
|
25
7
|
end
|
26
8
|
end
|
27
9
|
end
|
10
|
+
|
11
|
+
require "dry/transaction/step_adapters/map"
|
12
|
+
require "dry/transaction/step_adapters/raw"
|
13
|
+
require "dry/transaction/step_adapters/tee"
|
14
|
+
require "dry/transaction/step_adapters/try"
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Dry
|
2
2
|
module Transaction
|
3
|
-
|
3
|
+
class StepAdapters
|
4
4
|
# @api private
|
5
|
-
class Map
|
6
|
-
def call(*args, input)
|
7
|
-
Right(operation.call(*args, input))
|
5
|
+
class Map
|
6
|
+
def call(step, *args, input)
|
7
|
+
Right(step.operation.call(*args, input))
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
register :map, Map
|
11
|
+
register :map, Map.new
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -1,14 +1,22 @@
|
|
1
|
+
require "kleisli"
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Transaction
|
3
|
-
|
5
|
+
class StepAdapters
|
4
6
|
# @api private
|
5
|
-
class Raw
|
6
|
-
def call(*args, input)
|
7
|
-
operation.call(*args, input)
|
7
|
+
class Raw
|
8
|
+
def call(step, *args, input)
|
9
|
+
result = step.operation.call(*args, input)
|
10
|
+
|
11
|
+
unless result.is_a?(Kleisli::Either)
|
12
|
+
raise ArgumentError, "step +#{step.step_name}+ must return an Either object"
|
13
|
+
end
|
14
|
+
|
15
|
+
result
|
8
16
|
end
|
9
17
|
end
|
10
18
|
|
11
|
-
register :step, Raw
|
19
|
+
register :step, Raw.new
|
12
20
|
end
|
13
21
|
end
|
14
22
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module Dry
|
2
2
|
module Transaction
|
3
|
-
|
3
|
+
class StepAdapters
|
4
4
|
# @api private
|
5
|
-
class Tee
|
6
|
-
def call(*args, input)
|
7
|
-
operation.call(*args, input)
|
5
|
+
class Tee
|
6
|
+
def call(step, *args, input)
|
7
|
+
step.operation.call(*args, input)
|
8
8
|
Right(input)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
register :tee, Tee
|
12
|
+
register :tee, Tee.new
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,21 +1,20 @@
|
|
1
1
|
module Dry
|
2
2
|
module Transaction
|
3
|
-
|
3
|
+
class StepAdapters
|
4
4
|
# @api private
|
5
|
-
class Try
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
class Try
|
6
|
+
def call(step, *args, input)
|
7
|
+
unless step.options[:catch]
|
8
|
+
raise ArgumentError, "+try+ steps require one or more exception classes provided via +catch:+"
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
rescue *Array(options[:catch]) => e
|
11
|
+
Right(step.operation.call(*args, input))
|
12
|
+
rescue *Array(step.options[:catch]) => e
|
14
13
|
Left(e)
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
18
|
-
register :try, Try
|
17
|
+
register :try, Try.new
|
19
18
|
end
|
20
19
|
end
|
21
20
|
end
|
data/spec/examples.txt
CHANGED
@@ -1,41 +1,43 @@
|
|
1
1
|
example_id | status | run_time |
|
2
2
|
-------------------------------------------------------- | ------ | --------------- |
|
3
|
-
./spec/integration/
|
4
|
-
./spec/integration/passing_step_arguments_spec.rb[1:
|
5
|
-
./spec/integration/passing_step_arguments_spec.rb[1:
|
6
|
-
./spec/integration/
|
7
|
-
./spec/integration/publishing_step_events_spec.rb[1:1:
|
8
|
-
./spec/integration/publishing_step_events_spec.rb[1:2
|
9
|
-
./spec/integration/publishing_step_events_spec.rb[1:2:
|
3
|
+
./spec/integration/custom_step_adapters_spec.rb[1:1] | passed | 0.00032 seconds |
|
4
|
+
./spec/integration/passing_step_arguments_spec.rb[1:1:1] | passed | 0.001 seconds |
|
5
|
+
./spec/integration/passing_step_arguments_spec.rb[1:2:1] | passed | 0.00021 seconds |
|
6
|
+
./spec/integration/passing_step_arguments_spec.rb[1:3:1] | passed | 0.0002 seconds |
|
7
|
+
./spec/integration/publishing_step_events_spec.rb[1:1:1] | passed | 0.00089 seconds |
|
8
|
+
./spec/integration/publishing_step_events_spec.rb[1:1:2] | passed | 0.00323 seconds |
|
9
|
+
./spec/integration/publishing_step_events_spec.rb[1:2:1] | passed | 0.00071 seconds |
|
10
|
+
./spec/integration/publishing_step_events_spec.rb[1:2:2] | passed | 0.00032 seconds |
|
10
11
|
./spec/integration/transaction_spec.rb[1:1:1] | passed | 0.00166 seconds |
|
11
|
-
./spec/integration/transaction_spec.rb[1:1:2] | passed | 0.
|
12
|
-
./spec/integration/transaction_spec.rb[1:1:3] | passed | 0.
|
13
|
-
./spec/integration/transaction_spec.rb[1:1:4] | passed | 0.
|
14
|
-
./spec/integration/transaction_spec.rb[1:1:5] | passed | 0.
|
15
|
-
./spec/integration/transaction_spec.rb[1:2:1] | passed | 0.
|
16
|
-
./spec/integration/transaction_spec.rb[1:2:2] | passed | 0.
|
17
|
-
./spec/integration/transaction_spec.rb[1:2:3] | passed | 0.
|
18
|
-
./spec/integration/transaction_spec.rb[1:2:4] | passed | 0.
|
19
|
-
./spec/integration/transaction_spec.rb[1:2:5] | passed | 0.
|
20
|
-
./spec/integration/transaction_spec.rb[1:2:6] | passed | 0.
|
21
|
-
./spec/integration/transaction_spec.rb[1:3:1] | passed | 0.
|
22
|
-
./spec/integration/transaction_spec.rb[1:3:2] | passed | 0.
|
12
|
+
./spec/integration/transaction_spec.rb[1:1:2] | passed | 0.00031 seconds |
|
13
|
+
./spec/integration/transaction_spec.rb[1:1:3] | passed | 0.00024 seconds |
|
14
|
+
./spec/integration/transaction_spec.rb[1:1:4] | passed | 0.00033 seconds |
|
15
|
+
./spec/integration/transaction_spec.rb[1:1:5] | passed | 0.00036 seconds |
|
16
|
+
./spec/integration/transaction_spec.rb[1:2:1] | passed | 0.00029 seconds |
|
17
|
+
./spec/integration/transaction_spec.rb[1:2:2] | passed | 0.00023 seconds |
|
18
|
+
./spec/integration/transaction_spec.rb[1:2:3] | passed | 0.00025 seconds |
|
19
|
+
./spec/integration/transaction_spec.rb[1:2:4] | passed | 0.00025 seconds |
|
20
|
+
./spec/integration/transaction_spec.rb[1:2:5] | passed | 0.00026 seconds |
|
21
|
+
./spec/integration/transaction_spec.rb[1:2:6] | passed | 0.00024 seconds |
|
22
|
+
./spec/integration/transaction_spec.rb[1:3:1] | passed | 0.00242 seconds |
|
23
|
+
./spec/integration/transaction_spec.rb[1:3:2] | passed | 0.00023 seconds |
|
23
24
|
./spec/integration/transaction_spec.rb[1:3:3] | passed | 0.00022 seconds |
|
24
|
-
./spec/
|
25
|
-
./spec/unit/sequence_spec.rb[1:1:
|
26
|
-
./spec/unit/sequence_spec.rb[1:1:
|
27
|
-
./spec/unit/sequence_spec.rb[1:1:
|
28
|
-
./spec/unit/sequence_spec.rb[1:
|
29
|
-
./spec/unit/sequence_spec.rb[1:2:
|
30
|
-
./spec/unit/sequence_spec.rb[1:2:
|
31
|
-
./spec/unit/sequence_spec.rb[1:2:
|
32
|
-
./spec/unit/sequence_spec.rb[1:
|
33
|
-
./spec/unit/sequence_spec.rb[1:3:
|
34
|
-
./spec/unit/sequence_spec.rb[1:
|
35
|
-
./spec/unit/sequence_spec.rb[1:4:
|
36
|
-
./spec/unit/sequence_spec.rb[1:4:
|
37
|
-
./spec/unit/sequence_spec.rb[1:4:
|
38
|
-
./spec/unit/sequence_spec.rb[1:4:
|
39
|
-
./spec/unit/sequence_spec.rb[1:4:5:
|
40
|
-
./spec/unit/sequence_spec.rb[1:4:
|
41
|
-
./spec/unit/sequence_spec.rb[1:4:6:
|
25
|
+
./spec/integration/transaction_spec.rb[1:4:1] | passed | 0.00026 seconds |
|
26
|
+
./spec/unit/sequence_spec.rb[1:1:1] | passed | 0.00028 seconds |
|
27
|
+
./spec/unit/sequence_spec.rb[1:1:2] | passed | 0.00017 seconds |
|
28
|
+
./spec/unit/sequence_spec.rb[1:1:3] | passed | 0.00015 seconds |
|
29
|
+
./spec/unit/sequence_spec.rb[1:1:4] | passed | 0.00017 seconds |
|
30
|
+
./spec/unit/sequence_spec.rb[1:2:1] | passed | 0.0003 seconds |
|
31
|
+
./spec/unit/sequence_spec.rb[1:2:2] | passed | 0.00021 seconds |
|
32
|
+
./spec/unit/sequence_spec.rb[1:2:3] | passed | 0.0023 seconds |
|
33
|
+
./spec/unit/sequence_spec.rb[1:2:4] | passed | 0.00161 seconds |
|
34
|
+
./spec/unit/sequence_spec.rb[1:3:1] | passed | 0.00018 seconds |
|
35
|
+
./spec/unit/sequence_spec.rb[1:3:2] | passed | 0.00018 seconds |
|
36
|
+
./spec/unit/sequence_spec.rb[1:4:1] | passed | 0.00027 seconds |
|
37
|
+
./spec/unit/sequence_spec.rb[1:4:2] | passed | 0.0003 seconds |
|
38
|
+
./spec/unit/sequence_spec.rb[1:4:3] | passed | 0.00017 seconds |
|
39
|
+
./spec/unit/sequence_spec.rb[1:4:4] | passed | 0.00014 seconds |
|
40
|
+
./spec/unit/sequence_spec.rb[1:4:5:1] | passed | 0.0002 seconds |
|
41
|
+
./spec/unit/sequence_spec.rb[1:4:5:2] | passed | 0.00018 seconds |
|
42
|
+
./spec/unit/sequence_spec.rb[1:4:6:1] | passed | 0.00021 seconds |
|
43
|
+
./spec/unit/sequence_spec.rb[1:4:6:2] | passed | 0.00021 seconds |
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RSpec.describe "Custom step adapters" do
|
2
|
+
let(:transaction) {
|
3
|
+
Dry.Transaction(container: container, step_adapters: Test::CustomStepAdapters) do
|
4
|
+
map :process
|
5
|
+
tee :persist
|
6
|
+
enqueue :deliver
|
7
|
+
end
|
8
|
+
}
|
9
|
+
|
10
|
+
let(:container) {
|
11
|
+
{
|
12
|
+
process: -> input { {name: input["name"], email: input["email"]} },
|
13
|
+
persist: -> input { Test::DB << input and true },
|
14
|
+
deliver: -> input { "Delivered email to #{input[:email]}" },
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
before do
|
19
|
+
Test::DB = []
|
20
|
+
Test::QUEUE = []
|
21
|
+
|
22
|
+
module Test
|
23
|
+
class CustomStepAdapters < Dry::Transaction::StepAdapters
|
24
|
+
register :enqueue, -> step, *args, input {
|
25
|
+
Test::QUEUE << step.operation.call(*args, input)
|
26
|
+
Right(input)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "supports custom step adapters" do
|
33
|
+
input = {"name" => "Jane", "email" => "jane@doe.com"}
|
34
|
+
transaction.call(input)
|
35
|
+
expect(Test::QUEUE).to include("Delivered email to jane@doe.com")
|
36
|
+
end
|
37
|
+
end
|
@@ -136,4 +136,16 @@ RSpec.describe "Transactions" do
|
|
136
136
|
expect(transaction.call(input).value).to eq "raw failure"
|
137
137
|
end
|
138
138
|
end
|
139
|
+
|
140
|
+
context "non-confirming raw step result" do
|
141
|
+
let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
|
142
|
+
|
143
|
+
before do
|
144
|
+
container[:verify] = -> input { "failure" }
|
145
|
+
end
|
146
|
+
|
147
|
+
it "raises an exception" do
|
148
|
+
expect { transaction.call(input) }.to raise_error(ArgumentError)
|
149
|
+
end
|
150
|
+
end
|
139
151
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
if RUBY_ENGINE == "ruby"
|
2
|
+
require "codeclimate-test-reporter"
|
3
|
+
CodeClimate::TestReporter.start
|
4
|
+
|
5
|
+
require "simplecov"
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter "/spec/"
|
8
|
+
end
|
5
9
|
end
|
6
10
|
|
7
11
|
require "dry-transaction"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-transaction
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.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: 2016-
|
11
|
+
date: 2016-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-container
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.8
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.8
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: kleisli
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +58,14 @@ dependencies:
|
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
61
|
+
version: 1.11.2
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
68
|
+
version: 1.11.2
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rake
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -127,7 +141,6 @@ files:
|
|
127
141
|
- lib/dry/transaction/sequence.rb
|
128
142
|
- lib/dry/transaction/step.rb
|
129
143
|
- lib/dry/transaction/step_adapters.rb
|
130
|
-
- lib/dry/transaction/step_adapters/base.rb
|
131
144
|
- lib/dry/transaction/step_adapters/map.rb
|
132
145
|
- lib/dry/transaction/step_adapters/raw.rb
|
133
146
|
- lib/dry/transaction/step_adapters/tee.rb
|
@@ -135,6 +148,7 @@ files:
|
|
135
148
|
- lib/dry/transaction/step_failure.rb
|
136
149
|
- lib/dry/transaction/version.rb
|
137
150
|
- spec/examples.txt
|
151
|
+
- spec/integration/custom_step_adapters_spec.rb
|
138
152
|
- spec/integration/passing_step_arguments_spec.rb
|
139
153
|
- spec/integration/publishing_step_events_spec.rb
|
140
154
|
- spec/integration/transaction_spec.rb
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module Dry
|
2
|
-
module Transaction
|
3
|
-
module StepAdapters
|
4
|
-
# @api private
|
5
|
-
class Base
|
6
|
-
attr_reader :operation
|
7
|
-
attr_reader :options
|
8
|
-
|
9
|
-
def initialize(operation, options)
|
10
|
-
@operation = operation
|
11
|
-
@options = options
|
12
|
-
end
|
13
|
-
|
14
|
-
def arity
|
15
|
-
operation.is_a?(Proc) ? operation.arity : operation.method(:call).arity
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|