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 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