seam 0.0.9 → 0.0.10
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.
- data/README.md +131 -5
- data/lib/seam/flow.rb +5 -6
- data/lib/seam/version.rb +1 -1
- data/spec/seam/flow_spec.rb +17 -2
- metadata +4 -4
data/README.md
CHANGED
@@ -1,6 +1,136 @@
|
|
1
1
|
# Seam
|
2
2
|
|
3
|
-
|
3
|
+
Simple workflows in Ruby.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Seam is meant for situations where you want to take some entity (user, order, ec.) through a long-running process that is comprised of multiple steps.
|
8
|
+
|
9
|
+
For example, if you want every new user a "hello" email after signup, then wait a few days, and then send a "gone so soon?" email if they haven't signed in again.
|
10
|
+
|
11
|
+
This gem provides some simple tools for building and executing this process.
|
12
|
+
It provides a way to define the process, break it up into separate components, and then send entities through the process.
|
13
|
+
|
14
|
+
####Define a workflow####
|
15
|
+
|
16
|
+
To start, define a workflow.
|
17
|
+
|
18
|
+
````
|
19
|
+
flow = Seam::Flow.new
|
20
|
+
flow.send_order_to_warehouse
|
21
|
+
flow.wait_for_order_to_be_shipped wait_up_to: 7.days
|
22
|
+
flow.send_shipping_email email_template: 'shipping_7'
|
23
|
+
flow.mark_order_as_fulfilled
|
24
|
+
````
|
25
|
+
|
26
|
+
A flow will convert any method call you make into a step that has to be completed.
|
27
|
+
|
28
|
+
You can also pass a hash to the method, which will be saved for later.
|
29
|
+
|
30
|
+
````
|
31
|
+
flow.wait_for_order_to_be_shipped wait_up_to: 7.days
|
32
|
+
````
|
33
|
+
|
34
|
+
####Starting an instance of the flow####
|
35
|
+
|
36
|
+
Starting an instance of the flow is done with "start":
|
37
|
+
|
38
|
+
````
|
39
|
+
flow.start order_id: '1234'
|
40
|
+
````
|
41
|
+
|
42
|
+
An instance of this effort was created and saved in whatever persistence is being used (in-memory by default).
|
43
|
+
|
44
|
+
This effort will start at the first step (send_order_to_warehouse) and then progress through the steps as they are completed.
|
45
|
+
|
46
|
+
"start" also returns the effort that was created, and it will look like this:
|
47
|
+
|
48
|
+
````
|
49
|
+
<Seam::Effort
|
50
|
+
@completed_steps=[],
|
51
|
+
@history=[],
|
52
|
+
@complete=false,
|
53
|
+
@id="1ecc4cbe-16af-45f6-8532-7f37493ec11c",
|
54
|
+
@created_at=2013-08-20 22:58:07 -0500,
|
55
|
+
@next_execute_at=2013-08-20 22:58:07 -0500,
|
56
|
+
@next_step="send_order_to_warehouse",
|
57
|
+
@flow={"steps"=>[{"name"=>"send_order_to_warehouse", "type"=>"do", "arguments"=>{}}, {"name"=>"wait_for_order_to_be_shipped", "type"=>"do", "arguments"=>{}}, {"name"=>"send_shipping_email", "type"=>"do", "arguments"=>{}}, {"name"=>"mark_order_as_fulfilled", "type"=>"do", "arguments"=>{}}]},
|
58
|
+
@data={"order_id"=>"1234"}>
|
59
|
+
````
|
60
|
+
|
61
|
+
So we have a unique instance of this flow and the instance has been saved in the database. The first step to be executed for this instance is "send_order_to_warehouse", so let's create a worker for this step.
|
62
|
+
|
63
|
+
####Defining workers for each step####
|
64
|
+
|
65
|
+
A workflow is comprised of steps, and each step needs a worker. Each worker will execute whatever it was meant to do, and then either:
|
66
|
+
|
67
|
+
1. Pass the workflow instance to the next step on the process, or
|
68
|
+
2. Delay the step execution for a later date, or
|
69
|
+
3. End the entire workflow process for the instance.
|
70
|
+
|
71
|
+
Since send_order_to_warehouse is the first step in this workflow, let's build the worker for it first:
|
72
|
+
|
73
|
+
````
|
74
|
+
class SendOrderToWarehouseWorker < Seam::Worker
|
75
|
+
def process
|
76
|
+
# the current workflow instance is available as "effort"
|
77
|
+
order = Order.find effort.data['order_id']
|
78
|
+
warehouse_service.send order
|
79
|
+
|
80
|
+
# by default, if this worker completes with no error the workflow instance will be sent to the next step
|
81
|
+
end
|
82
|
+
end
|
83
|
+
````
|
84
|
+
|
85
|
+
If you name your class as a camel-case version of the step, Seam will automatically bind up the worker to the step.
|
86
|
+
|
87
|
+
To execute the worker, use:
|
88
|
+
|
89
|
+
````
|
90
|
+
SendOrderToWarehouse.execute_all
|
91
|
+
````
|
92
|
+
|
93
|
+
This method will look for all workflow instances that are currently ready for the step in question.
|
94
|
+
|
95
|
+
####Progressing through the workflow####
|
96
|
+
|
97
|
+
By default, steps are considered as being completed when the worker completes successfully. There might be times where you don't want to go quickly, like the next step in this example:
|
98
|
+
|
99
|
+
````
|
100
|
+
class WaitForOrderToBeShippedWorker < Seam::Worker
|
101
|
+
def process
|
102
|
+
effort.data["shipping_status"] = # some method that returns the shipping status
|
103
|
+
unless effort.data["shipping_status"] == "shipped"
|
104
|
+
try_again_in 4.hours
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
````
|
109
|
+
|
110
|
+
"try_again_in" can be used to signal that the step has not been completed and should be retried later.
|
111
|
+
|
112
|
+
"eject" can also be used to signify that the entire workflow should be stopped, like so:
|
113
|
+
|
114
|
+
````
|
115
|
+
class WaitForOrderToBeShippedWorker < Seam::Worker
|
116
|
+
def process
|
117
|
+
effort.data["shipping_status"] = # some method that returns the shipping status
|
118
|
+
if effort.data["shipping_status"] == "cancelled"
|
119
|
+
eject # no need to continue!
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
````
|
124
|
+
|
125
|
+
####History####
|
126
|
+
|
127
|
+
As workflow instances progress through each step, the history of every operation will be stored. A history of the "data" block before and after each step run is also stored.
|
128
|
+
|
129
|
+
The history is available through:
|
130
|
+
|
131
|
+
````
|
132
|
+
effort.history
|
133
|
+
````
|
4
134
|
|
5
135
|
## Installation
|
6
136
|
|
@@ -16,10 +146,6 @@ Or install it yourself as:
|
|
16
146
|
|
17
147
|
$ gem install seam
|
18
148
|
|
19
|
-
## Usage
|
20
|
-
|
21
|
-
TODO: Write usage instructions here
|
22
|
-
|
23
149
|
## Contributing
|
24
150
|
|
25
151
|
1. Fork it
|
data/lib/seam/flow.rb
CHANGED
@@ -2,13 +2,12 @@ module Seam
|
|
2
2
|
class Flow
|
3
3
|
|
4
4
|
def initialize
|
5
|
-
@steps =
|
5
|
+
@steps = []
|
6
6
|
end
|
7
7
|
|
8
8
|
def method_missing(meth, *args, &blk)
|
9
9
|
meth = meth.to_s
|
10
|
-
|
11
|
-
@steps[meth] = args
|
10
|
+
@steps << { name: meth, arguments: args }
|
12
11
|
true
|
13
12
|
end
|
14
13
|
|
@@ -28,10 +27,10 @@ module Seam
|
|
28
27
|
end
|
29
28
|
|
30
29
|
def steps
|
31
|
-
@steps.each.map do |
|
32
|
-
Seam::Step.new( { name:
|
30
|
+
@steps.each.map do |step|
|
31
|
+
Seam::Step.new( { name: step[:name],
|
33
32
|
type: 'do',
|
34
|
-
arguments: arguments } )
|
33
|
+
arguments: step[:arguments] } )
|
35
34
|
|
36
35
|
end
|
37
36
|
end
|
data/lib/seam/version.rb
CHANGED
data/spec/seam/flow_spec.rb
CHANGED
@@ -109,8 +109,23 @@ describe "flow" do
|
|
109
109
|
flow = Seam::Flow.new
|
110
110
|
flow.do_something.must_equal true
|
111
111
|
flow.steps.count.must_equal 1
|
112
|
-
flow.do_something.must_equal
|
113
|
-
flow.steps.count.must_equal
|
112
|
+
flow.do_something.must_equal true
|
113
|
+
flow.steps.count.must_equal 2
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "repeating steps with different data" do
|
118
|
+
it "should only add the step once" do
|
119
|
+
flow = Seam::Flow.new
|
120
|
+
flow.do_something(special_id: 'one').must_equal true
|
121
|
+
flow.do_something( { special_id: 'two' }, 4).must_equal true
|
122
|
+
flow.steps.count.must_equal 2
|
123
|
+
|
124
|
+
flow.steps[0].arguments.count.must_equal 1
|
125
|
+
flow.steps[0].arguments[0].contrast_with!( { special_id: 'one' } )
|
126
|
+
flow.steps[1].arguments.count.must_equal 2
|
127
|
+
flow.steps[1].arguments[0].contrast_with!( { special_id: 'two' } )
|
128
|
+
flow.steps[1].arguments[1].must_equal 4
|
114
129
|
end
|
115
130
|
end
|
116
131
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seam
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
@@ -227,7 +227,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
227
227
|
version: '0'
|
228
228
|
segments:
|
229
229
|
- 0
|
230
|
-
hash: -
|
230
|
+
hash: -3288944774515324226
|
231
231
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
232
232
|
none: false
|
233
233
|
requirements:
|
@@ -236,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
236
236
|
version: '0'
|
237
237
|
segments:
|
238
238
|
- 0
|
239
|
-
hash: -
|
239
|
+
hash: -3288944774515324226
|
240
240
|
requirements: []
|
241
241
|
rubyforge_project:
|
242
242
|
rubygems_version: 1.8.25
|