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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2527666eec64444e35bf101c6b084c6a4397a20b
4
- data.tar.gz: da3bd9e8d2f51dbbb758d99565858f2cd9aeb5ce
3
+ metadata.gz: 543d46be1752e9734d13693aac9feffc8723eee0
4
+ data.tar.gz: 03d4914f2f692cde520ee6988098e665ba2d80e5
5
5
  SHA512:
6
- metadata.gz: 25154eb134c0f970e4f23fe896278b94a87a68fc5edb1e8836a87a37b94f9226fe76da2a8b39283b2853591969eae5f111912c93c7e30ff35d761bac50f91e9f
7
- data.tar.gz: 8e088f4e96b57d1f7cc80548cdfe764c72d29f8c9ee63411492b09d8ab0407ba49697f7357d579f0115e5c6d92233bc9c510025f28499438742d1e8d64c79d98
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
- def execute(result)
29
- puts "Hello, World!"
30
- end
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
- parameter :to, :name
52
+ parameter :to, :name
51
53
 
52
- def execute(result)
53
- puts "Hello, #{@name}!"
54
- end
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
- .will(SayHello)
62
- .to("Ryan")
63
+ .will(SayHello)
64
+ .to("Ryan")
63
65
 
64
66
  # or
65
67
 
66
68
  plan = CarryOut
67
- .will(SayHello)
68
- .to { "Ryan" }
69
+ .will(SayHello)
70
+ .to { "Ryan" }
69
71
  ```
70
72
 
71
73
  And execute the same way as above.
72
74
 
73
- ### Artifacts and References
74
- Execution units can publish artifacts to the plan's result. Parameter blocks can be used to pass these artifacts on to subsequent execution units in the plan.
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
- parameter :items
112
+ parameter :items
79
113
 
80
- def execute(result)
81
- result.add :contents, @items
82
- end
114
+ def execute(result)
115
+ result.add :contents, @items
116
+ end
83
117
  end
84
118
 
85
119
  class CalculateSubtotal < CarryOut::Unit
86
- parameters :items
120
+ parameters :items
87
121
 
88
- def execut(result)
89
- subtotal = items.inject { |sum, item| sum + item.price }
90
- result.add :subtotal, subtotal
91
- end
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
- puts "Subtotal: #{result.artifacts[:invoice][:subtotal]}"
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.
@@ -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
@@ -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|
@@ -1,3 +1,3 @@
1
1
  module CarryOut
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
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.1
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-23 00:00:00.000000000 Z
11
+ date: 2017-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler