fluxo 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +233 -4
- data/fluxo.gemspec +2 -1
- data/lib/fluxo/config.rb +4 -4
- data/lib/fluxo/operation/attributes.rb +8 -8
- data/lib/fluxo/operation.rb +59 -11
- data/lib/fluxo/result.rb +8 -7
- data/lib/fluxo/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9453729318ec0b9d59e2833d19a4383c4b2f1bb241a8f97774c3f3b2cb4e6654
|
4
|
+
data.tar.gz: b428e33f29ababb0e2099eb9807f295fb899c965088ab2e650c111961d24c76a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ce303be1b5fa233fb9fc0e72eccebafd6d36e5ac81b2f6b8b1796c07c63bc5cbc097ed5fa7f48fdaf3457d3414335b8bbd1a63765557e7f3de0279a0dbb78ba
|
7
|
+
data.tar.gz: 514da4e79141fae0847ef16e2f4e9c67ac0246de87415277c657c93471cfe444a6eca8ec332b7a64aaa6286b61375b80d46ce49185fb5942b887d5a01ea4a271
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Fluxo
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Provides a simple and powerful way to create operations service objects for complex workflows.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -22,7 +20,238 @@ Or install it yourself as:
|
|
22
20
|
|
23
21
|
## Usage
|
24
22
|
|
25
|
-
|
23
|
+
Minimal operation definition:
|
24
|
+
```ruby
|
25
|
+
class MyOperation < Fluxo::Operation
|
26
|
+
def call!(**)
|
27
|
+
Success(:ok)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
And then just use the opperation by calling:
|
33
|
+
```
|
34
|
+
result = MyOperation.call
|
35
|
+
result.success? # => true
|
36
|
+
result.value # => :ok
|
37
|
+
```
|
38
|
+
|
39
|
+
In order to execute an operation with parameters, you must first define list of attributes:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class MyOperation < Fluxo::Operation
|
43
|
+
attributes :param1, :param2
|
44
|
+
|
45
|
+
def call!(param1:, param2:)
|
46
|
+
Success(:ok)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
or use the shortcut for defining attributes:
|
52
|
+
```ruby
|
53
|
+
class MyOperation < Fluxo::Operation(:param1, :param2)
|
54
|
+
def call!(param1:, param2:)
|
55
|
+
Success(:ok)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
### Operation Result
|
61
|
+
|
62
|
+
The execution result of an operation is a `Fluxo::Result` object. There are three types of results:
|
63
|
+
* `:ok`: the operation was successful
|
64
|
+
* `:failure`: the operation failed
|
65
|
+
* `:exception`: the operation raised an error
|
66
|
+
|
67
|
+
Use the `Success` and `Failure` methods to create results accordingly.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class AgeCheckOperation < Fluxo::Operation(:age)
|
71
|
+
def call!(age:)
|
72
|
+
age >= 18 ? Success('ok') : Failure('too young')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
result = AgeCheckOperation.call(age: 16) # #<Fluxo::Result @value="too young", @type=:failure>
|
77
|
+
result.success? # false
|
78
|
+
result.error? # false
|
79
|
+
result.failure? # true
|
80
|
+
result.value # "too young"
|
81
|
+
|
82
|
+
result = AgeCheckOperation.call(age: 18) # #<Fluxo::Result @value="ok", @type=:ok>
|
83
|
+
result.success? # true
|
84
|
+
result.error? # false
|
85
|
+
result.failure? # false
|
86
|
+
result.value # "ok"
|
87
|
+
```
|
88
|
+
|
89
|
+
The `result` also provides `on_success`, `on_failure` and `on_error` methods to define callbacks for the `:ok` and `:failure` results.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
AgeCheckOperation.call(age: 18)
|
93
|
+
.on_success { |result| puts result.value }
|
94
|
+
.on_failure { |_result| puts "Sorry, you are too young" }
|
95
|
+
```
|
96
|
+
|
97
|
+
You can also define multiple callbacks for the opportunity result. The callbacks are executed in the order they were defined. You can filter which callbacks are executed by specifying an identifier to the `Success(id) { }` or `Failure(id) { }` methods along with its value as a block.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class AgeCategoriesOperation < Fluxo::Operation(:age)
|
101
|
+
def call!(age:)
|
102
|
+
case age
|
103
|
+
when 0..14
|
104
|
+
Failure(:child) { "Sorry, you are too young" }
|
105
|
+
when 15..17
|
106
|
+
Failure(:teenager) { "You are a teenager" }
|
107
|
+
when 18..65
|
108
|
+
Success(:adult) { "You are an adult" }
|
109
|
+
else
|
110
|
+
Success(:senior) { "You are a senior" }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
AgeCategoriesOperation.call(age: 18) \
|
116
|
+
.on_success { |_result| puts "Great, you are an adult" } \
|
117
|
+
.on_success(:senior) { |_result| puts "Enjoy your retirement" } \
|
118
|
+
.on_success(:adult, :senior) { |_result| puts "Allowed access" } \
|
119
|
+
.on_failure { |_result| puts "Sorry, you are too young" } \
|
120
|
+
.on_failure(:teenager) { |_result| puts "Almost there, you are a teenager" }
|
121
|
+
# The above example will print:
|
122
|
+
# Great, you are an adult
|
123
|
+
# Allowed access
|
124
|
+
```
|
125
|
+
|
126
|
+
### Operation Flow
|
127
|
+
|
128
|
+
Once things become more complex, you can use can define a `flow` with a list of steps to be executed:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class ArithmeticOperation < Fluxo::Operation(:num)
|
132
|
+
flow :normalize, :plus_one, :double, :square, :wrap
|
133
|
+
|
134
|
+
def normalize(num:)
|
135
|
+
Success(num: num.to_i)
|
136
|
+
end
|
137
|
+
|
138
|
+
def plus_one(num:)
|
139
|
+
return Failure('cannot be zero') if num == 0
|
140
|
+
|
141
|
+
Success(num: num + 1)
|
142
|
+
end
|
143
|
+
|
144
|
+
def double(num:)
|
145
|
+
Success(num: num * 2)
|
146
|
+
end
|
147
|
+
|
148
|
+
def square(num:)
|
149
|
+
Success(num: num * num)
|
150
|
+
end
|
151
|
+
|
152
|
+
def wrap(num:)
|
153
|
+
Success(num)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
ArithmeticOperation.call(num: 1) \
|
158
|
+
.on_success { |result| puts "Result: #{result.value}" }
|
159
|
+
# Result: 16
|
160
|
+
```
|
161
|
+
|
162
|
+
Notice that the value of each step is passed to the next step as an argument. And the last step is always the result of the operation.
|
163
|
+
|
164
|
+
By default you can only pass defined attributes to the steps. You may want to pass transient attributes to the steps. You can do this by specifying a `transient_attributes` option to the operation class:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
class CreateUserOperation < Fluxo::Operation(:name, :age)
|
168
|
+
flow :build, :save
|
169
|
+
|
170
|
+
def build(name:, age:)
|
171
|
+
user = User.new(name: name, age: age)
|
172
|
+
Success(user: user)
|
173
|
+
end
|
174
|
+
|
175
|
+
def save(user:, **)
|
176
|
+
return Failure(user.errors) unless user.save
|
177
|
+
|
178
|
+
Success(user: user)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
This is useful to make the flow data transparent to the operation. But you can also disable this by setting the `strict_transient_attributes` option to `false` under the Operation class or the global configuration.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class CreateUserOperation < Fluxo::Operation(:name, :age)
|
187
|
+
self.strict_transient_attributes = false
|
188
|
+
# ...
|
189
|
+
end
|
190
|
+
# or globally
|
191
|
+
Fluxo.config do |config|
|
192
|
+
config.strict_attributes = false
|
193
|
+
config.strict_transient_attributes = false
|
194
|
+
end
|
195
|
+
# or even
|
196
|
+
Fluxo.config.strict_transient_attributes = false
|
197
|
+
```
|
198
|
+
|
199
|
+
### Operation Groups
|
200
|
+
|
201
|
+
Another very useful feature of Fluxo is the ability to group operations steps. Imagine that you want to execute a bunch of operations in a single transaction. You can do this by defining a the group method and specifying the steps to be executed in the group.
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
class CreateUserOperation < Fluxo::Operation(:name, :email)
|
205
|
+
transient_attributes :user, :profile
|
206
|
+
|
207
|
+
flow :build, {transaction: %i[save_user save_profile]}, :enqueue_job
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def transaction(**kwargs, &block)
|
212
|
+
ActiveRecord::Base.transaction do
|
213
|
+
result = block.call(**kwargs)
|
214
|
+
raise(ActiveRecord::Rollback) unless result.success?
|
215
|
+
end
|
216
|
+
result
|
217
|
+
end
|
218
|
+
|
219
|
+
def build(name:, email:)
|
220
|
+
user = User.new(name: name, email: email)
|
221
|
+
Success(user: user)
|
222
|
+
end
|
223
|
+
|
224
|
+
def save_user(user:, **)
|
225
|
+
return Failure(user.errors) unless user.save
|
226
|
+
|
227
|
+
Success(user: user)
|
228
|
+
end
|
229
|
+
|
230
|
+
def save_profile(user:, **)
|
231
|
+
UserProfile.create!(user: user)
|
232
|
+
Success()
|
233
|
+
end
|
234
|
+
|
235
|
+
def enqueue_job(user:, **)
|
236
|
+
UserJob.perform_later(user.id)
|
237
|
+
|
238
|
+
Success(user)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
### Configuration
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
Fluxo.config do |config|
|
247
|
+
config.wrap_falsey_result = false
|
248
|
+
config.wrap_truthy_result = false
|
249
|
+
config.strict_attributes = true
|
250
|
+
config.strict_transient_attributes = true
|
251
|
+
config.error_handlers << ->(result) { Honeybadger.notify(result.value) }
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
26
255
|
|
27
256
|
## Development
|
28
257
|
|
data/fluxo.gemspec
CHANGED
@@ -7,7 +7,8 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.email = ["mgzmaster@gmail.com"]
|
8
8
|
|
9
9
|
spec.summary = "Simple Ruby DSL to create operation service objects."
|
10
|
-
spec.description = "
|
10
|
+
spec.description = "Provides a simple and powerful way to create operations service objects for complex workflows."
|
11
|
+
|
11
12
|
spec.homepage = "https://github.com/marcosgz/fluxo"
|
12
13
|
spec.license = "MIT"
|
13
14
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
data/lib/fluxo/config.rb
CHANGED
@@ -17,17 +17,17 @@ module Fluxo
|
|
17
17
|
attr_accessor :wrap_truthy_result
|
18
18
|
|
19
19
|
# When set to true, the operation will not validate the transient_attributes defition during the flow step execution.
|
20
|
-
attr_accessor :
|
20
|
+
attr_accessor :strict_transient_attributes
|
21
21
|
|
22
22
|
# When set to true, the operation will not validate attributes definition before calling the operation.
|
23
|
-
attr_accessor :
|
23
|
+
attr_accessor :strict_attributes
|
24
24
|
|
25
25
|
def initialize
|
26
26
|
@error_handlers = []
|
27
27
|
@wrap_falsey_result = false
|
28
28
|
@wrap_truthy_result = false
|
29
|
-
@
|
30
|
-
@
|
29
|
+
@strict_transient_attributes = true
|
30
|
+
@strict_attributes = true
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -11,21 +11,21 @@ module Fluxo
|
|
11
11
|
attr_reader :validations_proxy
|
12
12
|
|
13
13
|
# When set to true, the operation will not validate the transient_attributes defition during the flow step execution.
|
14
|
-
attr_writer :
|
14
|
+
attr_writer :strict_transient_attributes
|
15
15
|
|
16
16
|
# When set to true, the operation will not validate attributes definition before calling the operation.
|
17
|
-
attr_writer :
|
17
|
+
attr_writer :strict_attributes
|
18
18
|
|
19
|
-
def
|
20
|
-
return @
|
19
|
+
def strict_attributes?
|
20
|
+
return @strict_attributes if defined?(@strict_attributes)
|
21
21
|
|
22
|
-
Fluxo.config.
|
22
|
+
Fluxo.config.strict_attributes
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
return @
|
25
|
+
def strict_transient_attributes?
|
26
|
+
return @strict_transient_attributes if defined?(@strict_transient_attributes)
|
27
27
|
|
28
|
-
Fluxo.config.
|
28
|
+
Fluxo.config.strict_transient_attributes
|
29
29
|
end
|
30
30
|
|
31
31
|
def validations
|
data/lib/fluxo/operation.rb
CHANGED
@@ -4,6 +4,8 @@ require_relative "operation/constructor"
|
|
4
4
|
require_relative "operation/attributes"
|
5
5
|
|
6
6
|
module Fluxo
|
7
|
+
# I know that the underline instance method name is not the best, but I don't want to
|
8
|
+
# conflict with the Operation step methods that are going to inherit this class.
|
7
9
|
class Operation
|
8
10
|
include Attributes
|
9
11
|
include Constructor
|
@@ -40,13 +42,24 @@ module Fluxo
|
|
40
42
|
# Calls step-method by step-method always passing the value to the next step
|
41
43
|
# If one of the methods is a failure stop the execution and return a result.
|
42
44
|
def __execute_flow__(steps: [], attributes: {})
|
43
|
-
transient_attributes = attributes.dup
|
45
|
+
transient_attributes, transient_ids = attributes.dup, {ok: [], failure: [], exception: []}
|
44
46
|
__validate_attributes__(first_step: steps.first, attributes: transient_attributes)
|
45
47
|
|
46
48
|
result = nil
|
47
49
|
steps.unshift(:__validate__) if self.class.validations_proxy # add validate step before the first step
|
48
50
|
steps.each_with_index do |step, idx|
|
49
|
-
|
51
|
+
if step.is_a?(Hash)
|
52
|
+
step.each do |group_method, group_steps|
|
53
|
+
send(group_method, **transient_attributes) do |group_attrs|
|
54
|
+
result = __execute_flow__(steps: group_steps, attributes: (group_attrs || transient_attributes))
|
55
|
+
end
|
56
|
+
break unless result.success?
|
57
|
+
end
|
58
|
+
else
|
59
|
+
result = __wrap_result__(send(step, **transient_attributes))
|
60
|
+
transient_ids.fetch(result.type).push(*result.ids)
|
61
|
+
end
|
62
|
+
|
50
63
|
break unless result.success?
|
51
64
|
|
52
65
|
if steps[idx + 1]
|
@@ -57,7 +70,7 @@ module Fluxo
|
|
57
70
|
)
|
58
71
|
end
|
59
72
|
end
|
60
|
-
result
|
73
|
+
result.tap { |r| r.ids = transient_ids.fetch(r.type).uniq }
|
61
74
|
end
|
62
75
|
|
63
76
|
# @param value_or_result_id [Any] The value for the result or the id when the result comes from block
|
@@ -90,47 +103,82 @@ module Fluxo
|
|
90
103
|
|
91
104
|
private
|
92
105
|
|
106
|
+
# Validates the operation was called with all the required keyword arguments.
|
107
|
+
# @param first_step [Symbol, Hash] The first step method
|
108
|
+
# @param attributes [Hash] The attributes to validate
|
109
|
+
# @return [void]
|
110
|
+
# @raise [MissingAttributeError] When a required attribute is missing
|
93
111
|
def __validate_attributes__(attributes:, first_step:)
|
94
|
-
if
|
112
|
+
if self.class.strict_attributes? && (extra = attributes.keys - self.class.attribute_names).any?
|
95
113
|
raise NotDefinedAttributeError, <<~ERROR
|
96
114
|
The following attributes are not defined: #{extra.join(", ")}
|
97
115
|
|
98
116
|
You can use the #{self.class.name}.attributes method to specify list of allowed attributes.
|
99
|
-
Or you can disable strict attributes mode by setting the
|
117
|
+
Or you can disable strict attributes mode by setting the strict_attributes to true.
|
100
118
|
ERROR
|
101
119
|
end
|
102
120
|
|
103
|
-
|
104
|
-
|
121
|
+
__expand_step_method__(first_step).each do |step|
|
122
|
+
method(step).parameters.select { |type, _| type == :keyreq }.each do |(_type, name)|
|
123
|
+
raise(MissingAttributeError, "Missing :#{name} attribute on #{self.class.name}#{step} step method.") unless attributes.key?(name)
|
124
|
+
end
|
105
125
|
end
|
106
126
|
end
|
107
127
|
|
128
|
+
# Merge the result attributes with the new attributes. Also checks if the upcomming step
|
129
|
+
# has the required attributes and transient attributes to a valid execution.
|
130
|
+
# @param new_attributes [Hash] The new attributes
|
131
|
+
# @param old_attributes [Hash] The old attributes
|
132
|
+
# @param next_step [Symbol, Hash] The next step method
|
108
133
|
def __merge_result_attributes__(new_attributes:, old_attributes:, next_step:)
|
109
134
|
return old_attributes unless new_attributes.is_a?(Hash)
|
110
135
|
|
111
136
|
attributes = old_attributes.merge(new_attributes)
|
112
137
|
allowed_attrs = self.class.attribute_names + self.class.transient_attribute_names
|
113
|
-
if
|
138
|
+
if self.class.strict_transient_attributes? &&
|
114
139
|
(extra = attributes.keys - allowed_attrs).any?
|
115
140
|
raise NotDefinedAttributeError, <<~ERROR
|
116
141
|
The following transient attributes are not defined: #{extra.join(", ")}
|
117
142
|
|
118
143
|
You can use the #{self.class.name}.transient_attributes method to specify list of allowed attributes.
|
119
|
-
Or you can disable strict transient attributes mode by setting the
|
144
|
+
Or you can disable strict transient attributes mode by setting the strict_transient_attributes to true.
|
120
145
|
ERROR
|
121
146
|
end
|
122
147
|
|
123
|
-
|
124
|
-
|
148
|
+
__expand_step_method__(next_step).each do |step|
|
149
|
+
method(step).parameters.select { |type, _| type == :keyreq }.each do |(_type, name)|
|
150
|
+
raise(MissingAttributeError, "Missing :#{name} transient attribute on #{self.class.name}##{step} step method.") unless attributes.key?(name)
|
151
|
+
end
|
125
152
|
end
|
126
153
|
|
127
154
|
attributes
|
128
155
|
end
|
129
156
|
|
157
|
+
# Return the step method as an array. When it's a hash it suppose to be a
|
158
|
+
# be a step group. In this case return its first key and its first value as
|
159
|
+
# the array of step methods.
|
160
|
+
#
|
161
|
+
# @param step [Symbol, Hash] The step method name
|
162
|
+
def __expand_step_method__(step)
|
163
|
+
return [step] unless step.is_a?(Hash)
|
164
|
+
|
165
|
+
key, value = step.first
|
166
|
+
[key, Array(value).first].compact
|
167
|
+
end
|
168
|
+
|
169
|
+
# Execute active_model validation as a flow step.
|
170
|
+
# @param attributes [Hash] The attributes to validate
|
171
|
+
# @return [Fluxo::Result] The result of the validation
|
130
172
|
def __validate__(**attributes)
|
131
173
|
self.class.validations_proxy.validate!(self, **attributes)
|
132
174
|
end
|
133
175
|
|
176
|
+
# Wrap the step method result in a Fluxo::Result object.
|
177
|
+
#
|
178
|
+
# @param result [Fluxo::Result, *Object] The object to wrap
|
179
|
+
# @raise [Fluxo::InvalidResultError] When the result is not a Fluxo::Result config
|
180
|
+
# is set to not wrap results.
|
181
|
+
# @return [Fluxo::Result] The wrapped result
|
134
182
|
def __wrap_result__(result)
|
135
183
|
if result.is_a?(Fluxo::Result)
|
136
184
|
return result
|
data/lib/fluxo/result.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module Fluxo
|
4
4
|
class Result
|
5
|
-
attr_reader :operation, :type, :value
|
5
|
+
attr_reader :operation, :type, :value
|
6
|
+
attr_accessor :ids
|
6
7
|
|
7
8
|
# @param options [Hash]
|
8
9
|
# @option options [Fluxo::Operation] :operation The operation instance that gererated this result
|
@@ -31,16 +32,16 @@ module Fluxo
|
|
31
32
|
type == :exception
|
32
33
|
end
|
33
34
|
|
34
|
-
def on_success(
|
35
|
-
tap { yield(self) if success? && (
|
35
|
+
def on_success(*handler_ids)
|
36
|
+
tap { yield(self) if success? && (handler_ids.none? || (ids & handler_ids).any?) }
|
36
37
|
end
|
37
38
|
|
38
|
-
def on_failure(
|
39
|
-
tap { yield(self) if failure? && (
|
39
|
+
def on_failure(*handler_ids)
|
40
|
+
tap { yield(self) if failure? && (handler_ids.none? || (ids & handler_ids).any?) }
|
40
41
|
end
|
41
42
|
|
42
|
-
def on_error(
|
43
|
-
tap { yield(self) if error? && (
|
43
|
+
def on_error(*handler_ids)
|
44
|
+
tap { yield(self) if error? && (handler_ids.none? || (ids & handler_ids).any?) }
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
data/lib/fluxo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluxo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcos G. Zimmermann
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: standard
|
@@ -52,7 +52,8 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
description:
|
55
|
+
description: Provides a simple and powerful way to create operations service objects
|
56
|
+
for complex workflows.
|
56
57
|
email:
|
57
58
|
- mgzmaster@gmail.com
|
58
59
|
executables: []
|