carry_out 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +108 -23
- data/lib/carry_out/result.rb +1 -1
- data/lib/carry_out/unit.rb +16 -0
- data/lib/carry_out/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 543d46be1752e9734d13693aac9feffc8723eee0
|
4
|
+
data.tar.gz: 03d4914f2f692cde520ee6988098e665ba2d80e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac4aaf4de3a8a1a107cad42305b6b3f40f29059c650b0f7055c653d0c7519b6e5719f334f543f9c0c1113f5528c64a8aa3bc4d5b4e79881a463435d425e0eba2
|
7
|
+
data.tar.gz: 20f606a99d159642d18bc0eefbc47b099de04be5a45264b2059ec008e7391603a81c0cbebcd002eca27a4f96e5556f6755dc2e44375b1a0e9a548a13095efe80
|
data/README.md
CHANGED
@@ -25,9 +25,9 @@ Or install it yourself as:
|
|
25
25
|
Execution units extend CarryOut::Unit and should implement ```CarryOut::Unit#execute(result)```.
|
26
26
|
```ruby
|
27
27
|
class SayHello < CarryOut::Unit
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def execute(result)
|
29
|
+
puts "Hello, World!"
|
30
|
+
end
|
31
31
|
end
|
32
32
|
```
|
33
33
|
|
@@ -44,51 +44,85 @@ result = plan.execute
|
|
44
44
|
### Parameters
|
45
45
|
Execution units can be passed parameters statically during plan creation, or dynamically via a block.
|
46
46
|
|
47
|
+
#### parameter
|
48
|
+
|
47
49
|
Redefine the example above to greet someone by name:
|
48
50
|
```ruby
|
49
51
|
class SayHello < CarryOut::Unit
|
50
|
-
|
52
|
+
parameter :to, :name
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
def execute(result)
|
55
|
+
puts "Hello, #{@name}!"
|
56
|
+
end
|
55
57
|
end
|
56
58
|
```
|
57
59
|
|
58
60
|
Define the plan as:
|
59
61
|
```ruby
|
60
62
|
plan = CarryOut
|
61
|
-
|
62
|
-
|
63
|
+
.will(SayHello)
|
64
|
+
.to("Ryan")
|
63
65
|
|
64
66
|
# or
|
65
67
|
|
66
68
|
plan = CarryOut
|
67
|
-
|
68
|
-
|
69
|
+
.will(SayHello)
|
70
|
+
.to { "Ryan" }
|
69
71
|
```
|
70
72
|
|
71
73
|
And execute the same way as above.
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
+
#### appending_parameter
|
76
|
+
|
77
|
+
Appending parameters will convert the value of an existing parameter to an array and push new values into that array. These parameters can improve the readability of a plan, and are also helpful if creating a plan dynamically.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class SayHello < CarryOut::Unit
|
81
|
+
parameter :to, :names
|
82
|
+
appending_parameter :and, :names
|
83
|
+
|
84
|
+
def execute
|
85
|
+
puts "Hello, #{@names}.join(", ")}!"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
plan = CarryOut
|
90
|
+
.will(SayHello)
|
91
|
+
.to("John")
|
92
|
+
.and("Jane")
|
93
|
+
.and("Ryan")
|
94
|
+
```
|
95
|
+
|
96
|
+
Unlike `parameter`, `appending_parameter` must provide both a method name and an instance variable name.
|
97
|
+
|
98
|
+
A non-appending parameter does not need to be called (or even exist) in order for appending parameters to operate.
|
99
|
+
|
100
|
+
Calling the non-appending version of a parameter *after* calling the appending version will result in the array being lost, replaced by the explicit non-appending value provided.
|
101
|
+
|
102
|
+
A unit may wish to provide the syntactic sugar while ensuring the underlying instance variable is always an array. This can be accomplished by defining two (or more) appending parameters pointed at the same instance variable.
|
103
|
+
|
104
|
+
### Results and Artifact References
|
105
|
+
|
106
|
+
Plan executions return a `CarryOut::Result` object that contains any artifacts returned by units (in `Result#artifacts`), along with any errors raised (in `Result#errors`). If `errors` is empty, `Result#success?` will return `true`.
|
107
|
+
|
108
|
+
Parameter blocks can be used to pass result artifacts on to subsequent execution units in the plan.
|
75
109
|
|
76
110
|
```ruby
|
77
111
|
class AddToCart < CarryOut::Unit
|
78
|
-
|
112
|
+
parameter :items
|
79
113
|
|
80
|
-
|
81
|
-
|
82
|
-
|
114
|
+
def execute(result)
|
115
|
+
result.add :contents, @items
|
116
|
+
end
|
83
117
|
end
|
84
118
|
|
85
119
|
class CalculateSubtotal < CarryOut::Unit
|
86
|
-
|
120
|
+
parameters :items
|
87
121
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
122
|
+
def execut(result)
|
123
|
+
subtotal = items.inject { |sum, item| sum + item.price }
|
124
|
+
result.add :subtotal, subtotal
|
125
|
+
end
|
92
126
|
end
|
93
127
|
```
|
94
128
|
```ruby
|
@@ -99,10 +133,61 @@ plan = CarryOut
|
|
99
133
|
.items { |refs| refs[:cart][:contents] }
|
100
134
|
|
101
135
|
plan.execute do |result|
|
102
|
-
|
136
|
+
puts "Subtotal: #{result.artifacts[:invoice][:subtotal]}"
|
103
137
|
end
|
104
138
|
```
|
105
139
|
|
140
|
+
### Wrapping the Execution Context
|
141
|
+
|
142
|
+
Contexts can be "baked into" a plan for purposes such as ensuring files get closed, or keeping the entire plan within a database transaction.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
class FileContext
|
146
|
+
def initialize(file_path)
|
147
|
+
@file_path = file_path
|
148
|
+
end
|
149
|
+
|
150
|
+
def execute
|
151
|
+
File.open(@file_path, "r") do |f|
|
152
|
+
yield file: f
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
plan = CarryOut
|
158
|
+
.within(FileContext.new("path/to/file")) # Expects instance, not class
|
159
|
+
.will(DoAThing)
|
160
|
+
.with_file { |refs| refs[:file] }
|
161
|
+
```
|
162
|
+
|
163
|
+
The wrapping context can also be a block.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
plan = CarryOut
|
167
|
+
.within { |proc| ActiveRecordBase.transaction { proc.call } }
|
168
|
+
.will(CreateModel)
|
169
|
+
```
|
170
|
+
|
171
|
+
When using a block, `proc.call` can be used to seed the references hash in the same manner as `yield` in the first example.
|
172
|
+
|
173
|
+
Wrapper contexts will always be applied to an entire plan. If a plan has multiple phases that need to be wrapped in different contexts, it is better to create multiple plans and embed them together in a larger plan as shown below.
|
174
|
+
|
175
|
+
### Embedding Plans
|
176
|
+
|
177
|
+
A plan can be used in place of a `CarryOut::Unit`. This allows plans to be reused as part of larger strategies.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
inner_plan = CarryOut.will(SayHello)
|
181
|
+
|
182
|
+
outer_plan = CarryOut
|
183
|
+
.will(DisplayBanner)
|
184
|
+
.then(inner_plan)
|
185
|
+
```
|
186
|
+
|
187
|
+
Passing a plan to `#then` works similar to passing a `CarryOut::Unit` class or instance. If the `as` option is added, the inner plan's result artifacts will be added to the outer plan's result at the specified key.
|
188
|
+
|
189
|
+
**One caveat to be aware of**: There is no way to specify parameters for an embedded plan. If an embedded plan depends on an external context, `CarryOut#within` is sufficient to work around this limitation. However, there is currently no way for an inner plan to access an outer plan's artifacts. This is considered a bug and will be fixed in a future release.
|
190
|
+
|
106
191
|
## Motivation
|
107
192
|
|
108
193
|
I've been trying to keep my Rails controllers clean, but I prefer to avoid shoving inter-model business logic inside database models. The recommendation I most frequently run into is to move that kind of logic into something akin to service objects. I like that idea, but I want to keep my services small and composable, and I want to separate the "what" from the "how" of my logic.
|
data/lib/carry_out/result.rb
CHANGED
@@ -14,7 +14,7 @@ module CarryOut
|
|
14
14
|
artifacts[group] ||= {}
|
15
15
|
object.each { |k,v| artifacts[group][k] = v }
|
16
16
|
elsif !artifacts[group].nil?
|
17
|
-
artifacts[group] = [ artifacts[group], object ].flatten
|
17
|
+
artifacts[group] = [ artifacts[group], object ].flatten(1)
|
18
18
|
else
|
19
19
|
artifacts[group] = object
|
20
20
|
end
|
data/lib/carry_out/unit.rb
CHANGED
@@ -4,6 +4,22 @@ module CarryOut
|
|
4
4
|
def execute
|
5
5
|
end
|
6
6
|
|
7
|
+
def self.appending_parameter(method_name, var)
|
8
|
+
var = var.to_s
|
9
|
+
instance_var = "@#{var}"
|
10
|
+
|
11
|
+
define_method(method_name.to_sym) do |value|
|
12
|
+
existing = if instance_variable_defined?(instance_var)
|
13
|
+
[ instance_variable_get("@#{var.to_s}") ].flatten(1)
|
14
|
+
else
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
|
18
|
+
instance_variable_set(instance_var, (existing || []) << value)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
7
23
|
def self.parameter(method_name, var = nil)
|
8
24
|
unless var.nil?
|
9
25
|
define_method(method_name.to_sym) do |value|
|
data/lib/carry_out/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: carry_out
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Fields
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|