seam 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,136 @@
1
1
  # Seam
2
2
 
3
- TODO: Write a gem description
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
- return false if @steps[meth]
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 |name, arguments|
32
- Seam::Step.new( { name: 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
@@ -1,3 +1,3 @@
1
1
  module Seam
2
- VERSION = "0.0.9"
2
+ VERSION = "0.0.10"
3
3
  end
@@ -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 false
113
- flow.steps.count.must_equal 1
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.9
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-20 00:00:00.000000000 Z
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: -1805101925636728224
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: -1805101925636728224
239
+ hash: -3288944774515324226
240
240
  requirements: []
241
241
  rubyforge_project:
242
242
  rubygems_version: 1.8.25