seam 0.0.1
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/seam/effort.rb +72 -0
- data/lib/seam/flow.rb +44 -0
- data/lib/seam/mongo_db.rb +12 -0
- data/lib/seam/persistence.rb +24 -0
- data/lib/seam/step.rb +15 -0
- data/lib/seam/version.rb +3 -0
- data/lib/seam/worker.rb +56 -0
- data/lib/seam.rb +9 -0
- data/seam.gemspec +33 -0
- data/spec/seam/effort_spec.rb +43 -0
- data/spec/seam/flow_spec.rb +97 -0
- data/spec/seam/worker_spec.rb +583 -0
- data/spec/spec_helper.rb +15 -0
- metadata +266 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Darren Cauthon
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Seam
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'seam'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install seam
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/seam/effort.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Seam
|
2
|
+
class Effort
|
3
|
+
attr_accessor :completed_steps
|
4
|
+
attr_accessor :created_at
|
5
|
+
attr_accessor :complete
|
6
|
+
attr_accessor :id
|
7
|
+
attr_accessor :next_execute_at
|
8
|
+
attr_accessor :next_step
|
9
|
+
attr_accessor :flow
|
10
|
+
attr_accessor :data
|
11
|
+
attr_accessor :history
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def find effort_id
|
16
|
+
Seam::Persistence.find_by_effort_id effort_id
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_all_by_step step
|
20
|
+
Seam::Persistence.find_all_pending_executions_by_step step
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse document
|
24
|
+
effort = Effort.new
|
25
|
+
effort.id = document['id']
|
26
|
+
effort.created_at = Time.parse(document['created_at'].to_s)
|
27
|
+
effort.next_execute_at = document['next_execute_at']
|
28
|
+
effort.next_step = document['next_step']
|
29
|
+
effort.flow = HashWithIndifferentAccess.new document['flow']
|
30
|
+
effort.data = HashWithIndifferentAccess.new document['data']
|
31
|
+
effort.history = document['history'].map { |x| HashWithIndifferentAccess.new x }
|
32
|
+
effort.completed_steps = document['completed_steps']
|
33
|
+
effort.complete = document['complete']
|
34
|
+
effort
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@completed_steps = []
|
41
|
+
@history = []
|
42
|
+
@complete = false
|
43
|
+
end
|
44
|
+
|
45
|
+
def save
|
46
|
+
existing_record = Seam::Effort.find self.id
|
47
|
+
if existing_record
|
48
|
+
Seam::Persistence.save self
|
49
|
+
else
|
50
|
+
Seam::Persistence.create self
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def complete?
|
55
|
+
complete
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_hash
|
59
|
+
{
|
60
|
+
id: self.id,
|
61
|
+
created_at: self.created_at,
|
62
|
+
completed_steps: self.completed_steps,
|
63
|
+
next_execute_at: self.next_execute_at,
|
64
|
+
next_step: self.next_step,
|
65
|
+
flow: self.flow,
|
66
|
+
data: self.data,
|
67
|
+
history: self.history,
|
68
|
+
complete: self.complete,
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/seam/flow.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Seam
|
2
|
+
class Flow
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@steps = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(meth, *args, &blk)
|
9
|
+
@steps << [meth.to_s, args]
|
10
|
+
end
|
11
|
+
|
12
|
+
def start(data = {})
|
13
|
+
effort = Seam::Effort.new
|
14
|
+
effort.id = SecureRandom.uuid.to_s
|
15
|
+
effort.created_at = Time.parse(Time.now.to_s)
|
16
|
+
effort.next_execute_at = Time.parse(Time.now.to_s)
|
17
|
+
effort.next_step = self.steps.first.name.to_s
|
18
|
+
effort.flow = ActiveSupport::HashWithIndifferentAccess.new self.to_hash
|
19
|
+
effort.data = ActiveSupport::HashWithIndifferentAccess.new data
|
20
|
+
effort.save
|
21
|
+
effort
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
{
|
26
|
+
steps: self.steps.map { |x| x.to_hash }
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def steps
|
31
|
+
@steps.map do |x|
|
32
|
+
step = Seam::Step.new
|
33
|
+
step.name = x[0]
|
34
|
+
step.type = "do"
|
35
|
+
step.arguments = x[1]
|
36
|
+
if step.name.index('branch_on')
|
37
|
+
step.name += "_#{x[1][0]}"
|
38
|
+
step.type = "branch"
|
39
|
+
end
|
40
|
+
step
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Seam
|
2
|
+
module Persistence
|
3
|
+
def self.find_by_effort_id effort_id
|
4
|
+
document = Seam::MongoDb.collection.find( { id: effort_id } ).first
|
5
|
+
return nil unless document
|
6
|
+
Seam::Effort.parse document
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.find_all_pending_executions_by_step step
|
10
|
+
Seam::MongoDb.collection
|
11
|
+
.find( { next_step: step, next_execute_at: { '$lte' => Time.now } } )
|
12
|
+
.map { |x| Seam::Effort.parse x }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.save effort
|
16
|
+
Seam::MongoDb.collection.find( { id: effort.id } )
|
17
|
+
.update("$set" => effort.to_hash)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.create effort
|
21
|
+
Seam::MongoDb.collection.insert(effort.to_hash)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/seam/step.rb
ADDED
data/lib/seam/version.rb
ADDED
data/lib/seam/worker.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Seam
|
2
|
+
class Worker
|
3
|
+
def for(step)
|
4
|
+
@step = step
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute effort
|
8
|
+
@current_run = HashWithIndifferentAccess.new( {
|
9
|
+
started_at: Time.now,
|
10
|
+
step: @step.to_s,
|
11
|
+
data_before: effort.data,
|
12
|
+
} )
|
13
|
+
@current_effort = effort
|
14
|
+
process
|
15
|
+
@current_run[:data_after] = effort.data
|
16
|
+
@current_run[:stopped_at] = Time.now
|
17
|
+
@current_effort.history << @current_run
|
18
|
+
@current_effort.save
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute_all
|
22
|
+
Seam::Effort.find_all_by_step(@step.to_s).each do |effort|
|
23
|
+
execute effort
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def eject
|
28
|
+
@current_run[:result] = "eject"
|
29
|
+
@current_effort.complete = true
|
30
|
+
@current_effort.next_step = nil
|
31
|
+
@current_effort.save
|
32
|
+
end
|
33
|
+
|
34
|
+
def move_to_next_step
|
35
|
+
@current_run[:result] = "move_to_next_step"
|
36
|
+
@current_effort.completed_steps << @current_effort.next_step
|
37
|
+
|
38
|
+
steps = @current_effort.flow['steps'].map { |x| x['name'] }
|
39
|
+
|
40
|
+
next_step = steps[@current_effort.completed_steps.count]
|
41
|
+
@current_effort.next_step = next_step
|
42
|
+
@current_effort.complete = next_step.nil?
|
43
|
+
@current_effort.save
|
44
|
+
end
|
45
|
+
|
46
|
+
def try_again_in seconds
|
47
|
+
try_again_on = Time.now + seconds
|
48
|
+
|
49
|
+
@current_run[:result] = "try_again_in"
|
50
|
+
@current_run[:try_again_on] = try_again_on
|
51
|
+
|
52
|
+
@current_effort.next_execute_at = try_again_on
|
53
|
+
@current_effort.save
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/seam.rb
ADDED
data/seam.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'seam/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "seam"
|
8
|
+
spec.version = Seam::VERSION
|
9
|
+
spec.authors = ["Darren Cauthon"]
|
10
|
+
spec.email = ["darren@cauthon.com"]
|
11
|
+
spec.description = %q{Simple workflows}
|
12
|
+
spec.summary = %q{Simple workflows}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "json"
|
22
|
+
spec.add_runtime_dependency "active_support"
|
23
|
+
spec.add_runtime_dependency "i18n"
|
24
|
+
spec.add_runtime_dependency "moped"
|
25
|
+
spec.add_runtime_dependency "json"
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "subtle"
|
29
|
+
spec.add_development_dependency "contrast"
|
30
|
+
spec.add_development_dependency "mocha"
|
31
|
+
spec.add_development_dependency "contrast"
|
32
|
+
spec.add_development_dependency "timecop"
|
33
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Seam::Effort do
|
4
|
+
before do
|
5
|
+
test_moped_session['efforts'].drop
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:flow) do
|
9
|
+
f = Seam::Flow.new
|
10
|
+
f.step1
|
11
|
+
f.step2
|
12
|
+
f
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "updating an effort" do
|
16
|
+
it "should not create another document in the collection" do
|
17
|
+
first_effort = flow.start
|
18
|
+
test_moped_session['efforts'].find.count.must_equal 1
|
19
|
+
first_effort.save
|
20
|
+
test_moped_session['efforts'].find.count.must_equal 1
|
21
|
+
|
22
|
+
second_effort = flow.start
|
23
|
+
test_moped_session['efforts'].find.count.must_equal 2
|
24
|
+
second_effort.save
|
25
|
+
test_moped_session['efforts'].find.count.must_equal 2
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should update the information" do
|
29
|
+
first_effort = flow.start
|
30
|
+
second_effort = flow.start
|
31
|
+
|
32
|
+
first_effort.next_step = 'i_changed_the_first_one'
|
33
|
+
first_effort.save
|
34
|
+
first_effort.to_hash.contrast_with! Seam::Effort.find(first_effort.id).to_hash, [:id, :created_at]
|
35
|
+
second_effort.to_hash.contrast_with! Seam::Effort.find(second_effort.id).to_hash, [:id, :created_at]
|
36
|
+
|
37
|
+
second_effort.next_step = 'i_changed_the_second_one'
|
38
|
+
second_effort.save
|
39
|
+
first_effort.to_hash.contrast_with! Seam::Effort.find(first_effort.id).to_hash, [:id, :created_at]
|
40
|
+
second_effort.to_hash.contrast_with! Seam::Effort.find(second_effort.id).to_hash, [:id, :created_at]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "flow" do
|
4
|
+
before do
|
5
|
+
test_moped_session['efforts'].drop
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
Timecop.return
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "a useful example" do
|
13
|
+
let(:flow) do
|
14
|
+
f = Seam::Flow.new
|
15
|
+
f.wait_for_attempting_contact_stage limit: 2.weeks
|
16
|
+
f.determine_if_postcard_should_be_sent
|
17
|
+
f.send_postcard_if_necessary
|
18
|
+
f
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "steps that must be taken" do
|
22
|
+
before do
|
23
|
+
flow
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not throw an error" do
|
27
|
+
flow.steps.count.must_equal 3
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should set the name of the three steps" do
|
31
|
+
flow.steps[0].name.must_equal "wait_for_attempting_contact_stage"
|
32
|
+
flow.steps[1].name.must_equal "determine_if_postcard_should_be_sent"
|
33
|
+
flow.steps[2].name.must_equal "send_postcard_if_necessary"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set the step types of the three steps" do
|
37
|
+
flow.steps[0].type.must_equal "do"
|
38
|
+
flow.steps[1].type.must_equal "do"
|
39
|
+
flow.steps[2].type.must_equal "do"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should set the arguments as well" do
|
43
|
+
flow.steps[0].arguments.must_equal [{ limit: 14.days.to_i }]
|
44
|
+
flow.steps[1].arguments.must_equal []
|
45
|
+
flow.steps[2].arguments.must_equal []
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "a more useful example" do
|
51
|
+
|
52
|
+
describe "starting an effort" do
|
53
|
+
let(:flow) do
|
54
|
+
flow = Seam::Flow.new
|
55
|
+
flow.do_something
|
56
|
+
flow.do_something_else
|
57
|
+
flow
|
58
|
+
end
|
59
|
+
|
60
|
+
let(:now) { Time.parse('1/1/2011') }
|
61
|
+
|
62
|
+
before do
|
63
|
+
Timecop.freeze now
|
64
|
+
|
65
|
+
@expected_uuid = SecureRandom.uuid.to_s
|
66
|
+
SecureRandom.expects(:uuid).returns @expected_uuid
|
67
|
+
|
68
|
+
@effort = flow.start( { first_name: 'John' } )
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should mark no steps as completed" do
|
72
|
+
@effort.completed_steps.count.must_equal 0
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should stamp the effort with a uuid" do
|
76
|
+
@effort.id.must_equal @expected_uuid
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should stamp the create date" do
|
80
|
+
@effort.created_at.must_equal now
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should stamp the next execute date" do
|
84
|
+
@effort.next_execute_at.must_equal now
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should stamp the next step name" do
|
88
|
+
@effort.next_step.must_equal "do_something"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should save an effort in the db" do
|
92
|
+
effort = Seam::Effort.find @effort.id
|
93
|
+
effort.to_hash.contrast_with! @effort.to_hash, [:id, :created_at]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,583 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "worker" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
test_moped_session['efforts'].drop
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
Timecop.return
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "move_to_next_step" do
|
14
|
+
it "should work" do
|
15
|
+
flow = Seam::Flow.new
|
16
|
+
flow.apple
|
17
|
+
flow.orange
|
18
|
+
|
19
|
+
effort = flow.start( { first_name: 'John' } )
|
20
|
+
effort = Seam::Effort.find(effort.id)
|
21
|
+
|
22
|
+
effort.next_step.must_equal "apple"
|
23
|
+
|
24
|
+
apple_worker = Seam::Worker.new
|
25
|
+
apple_worker.for(:apple)
|
26
|
+
def apple_worker.process
|
27
|
+
move_to_next_step
|
28
|
+
end
|
29
|
+
|
30
|
+
apple_worker.execute effort
|
31
|
+
|
32
|
+
effort = Seam::Effort.find(effort.id)
|
33
|
+
effort.next_step.must_equal "orange"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "try_again_in" do
|
38
|
+
|
39
|
+
let(:effort) do
|
40
|
+
flow = Seam::Flow.new
|
41
|
+
flow.apple
|
42
|
+
flow.orange
|
43
|
+
|
44
|
+
e = flow.start( { first_name: 'John' } )
|
45
|
+
Seam::Effort.find(e.id)
|
46
|
+
end
|
47
|
+
|
48
|
+
before do
|
49
|
+
Timecop.freeze Time.parse('3/4/2013')
|
50
|
+
effort.next_step.must_equal "apple"
|
51
|
+
|
52
|
+
apple_worker = Seam::Worker.new
|
53
|
+
apple_worker.for(:apple)
|
54
|
+
def apple_worker.process
|
55
|
+
try_again_in 1.day
|
56
|
+
end
|
57
|
+
|
58
|
+
apple_worker.execute effort
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should not update the next step" do
|
62
|
+
fresh_effort = Seam::Effort.find(effort.id)
|
63
|
+
fresh_effort.next_step.must_equal "apple"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not update the next execute date" do
|
67
|
+
fresh_effort = Seam::Effort.find(effort.id)
|
68
|
+
fresh_effort.next_execute_at.must_equal Time.parse('4/4/2013')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "more copmlex example" do
|
73
|
+
|
74
|
+
let(:effort1) do
|
75
|
+
flow = Seam::Flow.new
|
76
|
+
flow.grape
|
77
|
+
flow.mango
|
78
|
+
|
79
|
+
e = flow.start( { status: 'Good' } )
|
80
|
+
Seam::Effort.find(e.id)
|
81
|
+
end
|
82
|
+
|
83
|
+
let(:effort2) do
|
84
|
+
flow = Seam::Flow.new
|
85
|
+
flow.grape
|
86
|
+
flow.mango
|
87
|
+
|
88
|
+
e = flow.start( { status: 'Bad' } )
|
89
|
+
Seam::Effort.find(e.id)
|
90
|
+
end
|
91
|
+
|
92
|
+
before do
|
93
|
+
Timecop.freeze Time.parse('1/6/2013')
|
94
|
+
|
95
|
+
apple_worker = Seam::Worker.new
|
96
|
+
apple_worker.for(:apple)
|
97
|
+
def apple_worker.process
|
98
|
+
if @current_effort.data[:status] == 'Good'
|
99
|
+
move_to_next_step
|
100
|
+
else
|
101
|
+
try_again_in 1.day
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
apple_worker.execute effort1
|
106
|
+
apple_worker.execute effort2
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should move the first effort forward" do
|
110
|
+
fresh_effort = Seam::Effort.find(effort1.id)
|
111
|
+
fresh_effort.next_step.must_equal "mango"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should keep the second effort at the same step" do
|
115
|
+
fresh_effort = Seam::Effort.find(effort2.id)
|
116
|
+
fresh_effort.next_step.must_equal "grape"
|
117
|
+
fresh_effort.next_execute_at.must_equal Time.parse('2/6/2013')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "processing all pending steps for one effort" do
|
122
|
+
let(:effort1_creator) do
|
123
|
+
->() do
|
124
|
+
flow = Seam::Flow.new
|
125
|
+
flow.banana
|
126
|
+
flow.mango
|
127
|
+
|
128
|
+
e = flow.start
|
129
|
+
Seam::Effort.find(e.id)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
let(:effort2_creator) do
|
134
|
+
->() do
|
135
|
+
flow = Seam::Flow.new
|
136
|
+
flow.apple
|
137
|
+
flow.orange
|
138
|
+
|
139
|
+
e = flow.start
|
140
|
+
Seam::Effort.find(e.id)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
let(:apple_worker) do
|
145
|
+
apple_worker = Seam::Worker.new
|
146
|
+
apple_worker.for(:apple)
|
147
|
+
|
148
|
+
apple_worker.class_eval do
|
149
|
+
attr_accessor :count
|
150
|
+
end
|
151
|
+
|
152
|
+
def apple_worker.process
|
153
|
+
self.count += 1
|
154
|
+
end
|
155
|
+
|
156
|
+
apple_worker.count = 0
|
157
|
+
apple_worker
|
158
|
+
end
|
159
|
+
|
160
|
+
before do
|
161
|
+
Timecop.freeze Time.parse('1/6/2013')
|
162
|
+
|
163
|
+
effort1_creator.call
|
164
|
+
effort1_creator.call
|
165
|
+
effort1_creator.call
|
166
|
+
effort2_creator.call
|
167
|
+
effort2_creator.call
|
168
|
+
|
169
|
+
apple_worker.execute_all
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should call the apple worker for the record in question" do
|
173
|
+
apple_worker.count.must_equal 2
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "a more realistic example" do
|
178
|
+
|
179
|
+
let(:flow) do
|
180
|
+
flow = Seam::Flow.new
|
181
|
+
flow.wait_for_attempting_contact_stage
|
182
|
+
flow.determine_if_postcard_should_be_sent
|
183
|
+
flow.send_postcard_if_necessary
|
184
|
+
flow
|
185
|
+
end
|
186
|
+
|
187
|
+
let(:effort_creator) do
|
188
|
+
->() do
|
189
|
+
e = flow.start
|
190
|
+
Seam::Effort.find(e.id)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
let(:wait_for_attempting_contact_stage_worker) do
|
195
|
+
worker = Seam::Worker.new
|
196
|
+
worker.for(:wait_for_attempting_contact_stage)
|
197
|
+
|
198
|
+
def worker.process
|
199
|
+
@current_effort.data['hit 1'] ||= 0
|
200
|
+
@current_effort.data['hit 1'] += 1
|
201
|
+
move_to_next_step
|
202
|
+
end
|
203
|
+
|
204
|
+
worker
|
205
|
+
end
|
206
|
+
|
207
|
+
let(:determine_if_postcard_should_be_sent_worker) do
|
208
|
+
worker = Seam::Worker.new
|
209
|
+
worker.for(:determine_if_postcard_should_be_sent)
|
210
|
+
|
211
|
+
def worker.process
|
212
|
+
@current_effort.data['hit 2'] ||= 0
|
213
|
+
@current_effort.data['hit 2'] += 1
|
214
|
+
move_to_next_step
|
215
|
+
end
|
216
|
+
|
217
|
+
worker
|
218
|
+
end
|
219
|
+
|
220
|
+
let(:send_postcard_if_necessary_worker) do
|
221
|
+
worker = Seam::Worker.new
|
222
|
+
worker.for(:send_postcard_if_necessary)
|
223
|
+
|
224
|
+
def worker.process
|
225
|
+
@current_effort.data['hit 3'] ||= 0
|
226
|
+
@current_effort.data['hit 3'] += 1
|
227
|
+
move_to_next_step
|
228
|
+
end
|
229
|
+
|
230
|
+
worker
|
231
|
+
end
|
232
|
+
|
233
|
+
before do
|
234
|
+
Timecop.freeze Time.parse('1/6/2013')
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should progress through the story" do
|
238
|
+
|
239
|
+
# SETUP
|
240
|
+
effort = effort_creator.call
|
241
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
242
|
+
|
243
|
+
# FIRST WAVE
|
244
|
+
send_postcard_if_necessary_worker.execute_all
|
245
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
246
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
247
|
+
|
248
|
+
effort = Seam::Effort.find effort.id
|
249
|
+
effort.next_step.must_equal "determine_if_postcard_should_be_sent"
|
250
|
+
|
251
|
+
effort.complete?.must_equal false
|
252
|
+
|
253
|
+
# SECOND WAVE
|
254
|
+
send_postcard_if_necessary_worker.execute_all
|
255
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
256
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
257
|
+
|
258
|
+
effort = Seam::Effort.find effort.id
|
259
|
+
effort.next_step.must_equal "send_postcard_if_necessary"
|
260
|
+
|
261
|
+
# THIRD WAVE
|
262
|
+
send_postcard_if_necessary_worker.execute_all
|
263
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
264
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
265
|
+
|
266
|
+
effort = Seam::Effort.find effort.id
|
267
|
+
effort.next_step.must_equal nil
|
268
|
+
|
269
|
+
effort.data['hit 1'].must_equal 1
|
270
|
+
effort.data['hit 2'].must_equal 1
|
271
|
+
effort.data['hit 3'].must_equal 1
|
272
|
+
|
273
|
+
effort.complete?.must_equal true
|
274
|
+
|
275
|
+
# FUTURE WAVES
|
276
|
+
send_postcard_if_necessary_worker.execute_all
|
277
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
278
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
279
|
+
send_postcard_if_necessary_worker.execute_all
|
280
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
281
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
282
|
+
send_postcard_if_necessary_worker.execute_all
|
283
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
284
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
285
|
+
|
286
|
+
effort = Seam::Effort.find effort.id
|
287
|
+
effort.next_step.must_equal nil
|
288
|
+
|
289
|
+
effort.data['hit 1'].must_equal 1
|
290
|
+
effort.data['hit 2'].must_equal 1
|
291
|
+
effort.data['hit 3'].must_equal 1
|
292
|
+
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe "a more realistic example with waiting" do
|
297
|
+
|
298
|
+
let(:flow) do
|
299
|
+
flow = Seam::Flow.new
|
300
|
+
flow.wait_for_attempting_contact_stage
|
301
|
+
flow.determine_if_postcard_should_be_sent
|
302
|
+
flow.send_postcard_if_necessary
|
303
|
+
flow
|
304
|
+
end
|
305
|
+
|
306
|
+
let(:effort_creator) do
|
307
|
+
->() do
|
308
|
+
e = flow.start
|
309
|
+
Seam::Effort.find(e.id)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
let(:wait_for_attempting_contact_stage_worker) do
|
314
|
+
worker = Seam::Worker.new
|
315
|
+
worker.for(:wait_for_attempting_contact_stage)
|
316
|
+
|
317
|
+
def worker.process
|
318
|
+
@current_effort.data['hit 1'] ||= 0
|
319
|
+
@current_effort.data['hit 1'] += 1
|
320
|
+
if Time.now >= Time.parse('28/12/2013')
|
321
|
+
move_to_next_step
|
322
|
+
else
|
323
|
+
try_again_in 1.day
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
worker
|
328
|
+
end
|
329
|
+
|
330
|
+
let(:determine_if_postcard_should_be_sent_worker) do
|
331
|
+
worker = Seam::Worker.new
|
332
|
+
worker.for(:determine_if_postcard_should_be_sent)
|
333
|
+
|
334
|
+
def worker.process
|
335
|
+
@current_effort.data['hit 2'] ||= 0
|
336
|
+
@current_effort.data['hit 2'] += 1
|
337
|
+
move_to_next_step
|
338
|
+
end
|
339
|
+
|
340
|
+
worker
|
341
|
+
end
|
342
|
+
|
343
|
+
let(:send_postcard_if_necessary_worker) do
|
344
|
+
worker = Seam::Worker.new
|
345
|
+
worker.for(:send_postcard_if_necessary)
|
346
|
+
|
347
|
+
def worker.process
|
348
|
+
@current_effort.data['hit 3'] ||= 0
|
349
|
+
@current_effort.data['hit 3'] += 1
|
350
|
+
move_to_next_step
|
351
|
+
end
|
352
|
+
|
353
|
+
worker
|
354
|
+
end
|
355
|
+
|
356
|
+
before do
|
357
|
+
Timecop.freeze Time.parse('25/12/2013')
|
358
|
+
end
|
359
|
+
|
360
|
+
it "should progress through the story" do
|
361
|
+
|
362
|
+
# SETUP
|
363
|
+
effort = effort_creator.call
|
364
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
365
|
+
|
366
|
+
# FIRST DAY
|
367
|
+
send_postcard_if_necessary_worker.execute_all
|
368
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
369
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
370
|
+
|
371
|
+
effort = Seam::Effort.find effort.id
|
372
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
373
|
+
|
374
|
+
send_postcard_if_necessary_worker.execute_all
|
375
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
376
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
377
|
+
send_postcard_if_necessary_worker.execute_all
|
378
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
379
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
380
|
+
|
381
|
+
effort = Seam::Effort.find effort.id
|
382
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
383
|
+
|
384
|
+
Timecop.freeze Time.parse('29/12/2013')
|
385
|
+
|
386
|
+
send_postcard_if_necessary_worker.execute_all
|
387
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
388
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
389
|
+
|
390
|
+
effort = Seam::Effort.find effort.id
|
391
|
+
effort.next_step.must_equal "determine_if_postcard_should_be_sent"
|
392
|
+
effort.data['hit 1'].must_equal 2
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe "tracking history" do
|
397
|
+
|
398
|
+
let(:flow) do
|
399
|
+
flow = Seam::Flow.new
|
400
|
+
flow.wait_for_attempting_contact_stage
|
401
|
+
flow.determine_if_postcard_should_be_sent
|
402
|
+
flow.send_postcard_if_necessary
|
403
|
+
flow
|
404
|
+
end
|
405
|
+
|
406
|
+
let(:effort_creator) do
|
407
|
+
->(values = {}) do
|
408
|
+
e = flow.start values
|
409
|
+
Seam::Effort.find(e.id)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
let(:wait_for_attempting_contact_stage_worker) do
|
414
|
+
worker = Seam::Worker.new
|
415
|
+
worker.for(:wait_for_attempting_contact_stage)
|
416
|
+
|
417
|
+
def worker.process
|
418
|
+
@current_effort.data['hit 1'] ||= 0
|
419
|
+
@current_effort.data['hit 1'] += 1
|
420
|
+
if Time.now >= Time.parse('28/12/2013')
|
421
|
+
move_to_next_step
|
422
|
+
else
|
423
|
+
try_again_in 1.day
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
worker
|
428
|
+
end
|
429
|
+
|
430
|
+
let(:determine_if_postcard_should_be_sent_worker) do
|
431
|
+
worker = Seam::Worker.new
|
432
|
+
worker.for(:determine_if_postcard_should_be_sent)
|
433
|
+
|
434
|
+
def worker.process
|
435
|
+
@current_effort.data['hit 2'] ||= 0
|
436
|
+
@current_effort.data['hit 2'] += 1
|
437
|
+
move_to_next_step
|
438
|
+
end
|
439
|
+
|
440
|
+
worker
|
441
|
+
end
|
442
|
+
|
443
|
+
let(:send_postcard_if_necessary_worker) do
|
444
|
+
worker = Seam::Worker.new
|
445
|
+
worker.for(:send_postcard_if_necessary)
|
446
|
+
|
447
|
+
def worker.process
|
448
|
+
@current_effort.data['hit 3'] ||= 0
|
449
|
+
@current_effort.data['hit 3'] += 1
|
450
|
+
move_to_next_step
|
451
|
+
end
|
452
|
+
|
453
|
+
worker
|
454
|
+
end
|
455
|
+
|
456
|
+
before do
|
457
|
+
Timecop.freeze Time.parse('26/12/2013')
|
458
|
+
end
|
459
|
+
|
460
|
+
it "should progress through the story" do
|
461
|
+
|
462
|
+
# SETUP
|
463
|
+
effort = effort_creator.call({ first_name: 'DARREN' })
|
464
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
465
|
+
|
466
|
+
# FIRST DAY
|
467
|
+
send_postcard_if_necessary_worker.execute_all
|
468
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
469
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
470
|
+
|
471
|
+
effort = Seam::Effort.find effort.id
|
472
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
473
|
+
|
474
|
+
effort.history.count.must_equal 1
|
475
|
+
effort.history[0].contrast_with!( {
|
476
|
+
"started_at"=> Time.now,
|
477
|
+
"step"=>"wait_for_attempting_contact_stage",
|
478
|
+
"stopped_at" => Time.now,
|
479
|
+
"data_before" => { "first_name" => "DARREN" } ,
|
480
|
+
"data_after" => { "first_name" => "DARREN", "hit 1" => 1 }
|
481
|
+
} )
|
482
|
+
|
483
|
+
send_postcard_if_necessary_worker.execute_all
|
484
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
485
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
486
|
+
send_postcard_if_necessary_worker.execute_all
|
487
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
488
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
489
|
+
|
490
|
+
effort = Seam::Effort.find effort.id
|
491
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
492
|
+
|
493
|
+
effort.history.count.must_equal 1
|
494
|
+
effort.history[0].contrast_with!({"started_at"=> Time.now, "step"=>"wait_for_attempting_contact_stage", "stopped_at" => Time.now, "result" => "try_again_in", "try_again_on" => Time.now + 1.day } )
|
495
|
+
|
496
|
+
# THE NEXT DAY
|
497
|
+
Timecop.freeze Time.parse('27/12/2013')
|
498
|
+
|
499
|
+
send_postcard_if_necessary_worker.execute_all
|
500
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
501
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
502
|
+
|
503
|
+
effort = Seam::Effort.find effort.id
|
504
|
+
effort.next_step.must_equal "wait_for_attempting_contact_stage"
|
505
|
+
|
506
|
+
effort.history.count.must_equal 2
|
507
|
+
effort.history[1].contrast_with!({"started_at"=> Time.now, "step"=>"wait_for_attempting_contact_stage", "stopped_at" => Time.now, "result" => "try_again_in" } )
|
508
|
+
|
509
|
+
# THE NEXT DAY
|
510
|
+
Timecop.freeze Time.parse('28/12/2013')
|
511
|
+
|
512
|
+
send_postcard_if_necessary_worker.execute_all
|
513
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
514
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
515
|
+
|
516
|
+
effort = Seam::Effort.find effort.id
|
517
|
+
effort.next_step.must_equal "determine_if_postcard_should_be_sent"
|
518
|
+
|
519
|
+
effort.history.count.must_equal 3
|
520
|
+
effort.history[2].contrast_with!({"started_at"=> Time.now, "step"=>"wait_for_attempting_contact_stage", "stopped_at" => Time.now, "result" => "move_to_next_step" } )
|
521
|
+
|
522
|
+
# KEEP GOING
|
523
|
+
send_postcard_if_necessary_worker.execute_all
|
524
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
525
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
526
|
+
effort = Seam::Effort.find effort.id
|
527
|
+
effort.next_step.must_equal "send_postcard_if_necessary"
|
528
|
+
|
529
|
+
effort.history.count.must_equal 4
|
530
|
+
effort.history[3].contrast_with!({"started_at"=> Time.now, "step"=>"determine_if_postcard_should_be_sent", "stopped_at" => Time.now, "result" => "move_to_next_step" } )
|
531
|
+
|
532
|
+
# KEEP GOING
|
533
|
+
send_postcard_if_necessary_worker.execute_all
|
534
|
+
determine_if_postcard_should_be_sent_worker.execute_all
|
535
|
+
wait_for_attempting_contact_stage_worker.execute_all
|
536
|
+
effort = Seam::Effort.find effort.id
|
537
|
+
effort.next_step.must_equal nil
|
538
|
+
|
539
|
+
effort.history.count.must_equal 5
|
540
|
+
effort.history[4].contrast_with!({"started_at"=> Time.now, "step"=>"send_postcard_if_necessary", "stopped_at" => Time.now, "result" => "move_to_next_step" } )
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
describe "eject" do
|
545
|
+
|
546
|
+
let(:effort) do
|
547
|
+
flow = Seam::Flow.new
|
548
|
+
flow.apple
|
549
|
+
flow.orange
|
550
|
+
|
551
|
+
e = flow.start( { first_name: 'John' } )
|
552
|
+
Seam::Effort.find(e.id)
|
553
|
+
end
|
554
|
+
|
555
|
+
before do
|
556
|
+
Timecop.freeze Time.parse('5/11/2013')
|
557
|
+
effort.next_step.must_equal "apple"
|
558
|
+
|
559
|
+
apple_worker = Seam::Worker.new
|
560
|
+
apple_worker.for(:apple)
|
561
|
+
def apple_worker.process
|
562
|
+
eject
|
563
|
+
end
|
564
|
+
|
565
|
+
apple_worker.execute effort
|
566
|
+
end
|
567
|
+
|
568
|
+
it "should mark the step as completed" do
|
569
|
+
fresh_effort = Seam::Effort.find(effort.id)
|
570
|
+
fresh_effort.complete?.must_equal true
|
571
|
+
end
|
572
|
+
|
573
|
+
it "should mark the next step to nil" do
|
574
|
+
fresh_effort = Seam::Effort.find(effort.id)
|
575
|
+
fresh_effort.next_step.nil?.must_equal true
|
576
|
+
end
|
577
|
+
|
578
|
+
it "should mark the history" do
|
579
|
+
effort.history[0].contrast_with!({"step"=>"apple", "result" => "eject" } )
|
580
|
+
end
|
581
|
+
|
582
|
+
end
|
583
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/seam')
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/spec'
|
4
|
+
require 'minitest/pride'
|
5
|
+
require 'subtle'
|
6
|
+
require 'timecop'
|
7
|
+
require 'contrast'
|
8
|
+
require 'mocha/setup'
|
9
|
+
|
10
|
+
def test_moped_session
|
11
|
+
session = Moped::Session.new([ "127.0.0.1:27017" ])
|
12
|
+
session.use "seam_test"
|
13
|
+
end
|
14
|
+
|
15
|
+
Seam::MongoDb.set_collection test_moped_session['efforts']
|
metadata
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seam
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Darren Cauthon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: active_support
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: i18n
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: moped
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: json
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: bundler
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.3'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.3'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rake
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: subtle
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: contrast
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: mocha
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: contrast
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: timecop
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
description: Simple workflows
|
207
|
+
email:
|
208
|
+
- darren@cauthon.com
|
209
|
+
executables: []
|
210
|
+
extensions: []
|
211
|
+
extra_rdoc_files: []
|
212
|
+
files:
|
213
|
+
- .gitignore
|
214
|
+
- Gemfile
|
215
|
+
- LICENSE.txt
|
216
|
+
- README.md
|
217
|
+
- Rakefile
|
218
|
+
- lib/seam.rb
|
219
|
+
- lib/seam/effort.rb
|
220
|
+
- lib/seam/flow.rb
|
221
|
+
- lib/seam/mongo_db.rb
|
222
|
+
- lib/seam/persistence.rb
|
223
|
+
- lib/seam/step.rb
|
224
|
+
- lib/seam/version.rb
|
225
|
+
- lib/seam/worker.rb
|
226
|
+
- seam.gemspec
|
227
|
+
- spec/seam/effort_spec.rb
|
228
|
+
- spec/seam/flow_spec.rb
|
229
|
+
- spec/seam/worker_spec.rb
|
230
|
+
- spec/spec_helper.rb
|
231
|
+
homepage: ''
|
232
|
+
licenses:
|
233
|
+
- MIT
|
234
|
+
post_install_message:
|
235
|
+
rdoc_options: []
|
236
|
+
require_paths:
|
237
|
+
- lib
|
238
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
239
|
+
none: false
|
240
|
+
requirements:
|
241
|
+
- - ! '>='
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
segments:
|
245
|
+
- 0
|
246
|
+
hash: 1454651581186774459
|
247
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
248
|
+
none: false
|
249
|
+
requirements:
|
250
|
+
- - ! '>='
|
251
|
+
- !ruby/object:Gem::Version
|
252
|
+
version: '0'
|
253
|
+
segments:
|
254
|
+
- 0
|
255
|
+
hash: 1454651581186774459
|
256
|
+
requirements: []
|
257
|
+
rubyforge_project:
|
258
|
+
rubygems_version: 1.8.24
|
259
|
+
signing_key:
|
260
|
+
specification_version: 3
|
261
|
+
summary: Simple workflows
|
262
|
+
test_files:
|
263
|
+
- spec/seam/effort_spec.rb
|
264
|
+
- spec/seam/flow_spec.rb
|
265
|
+
- spec/seam/worker_spec.rb
|
266
|
+
- spec/spec_helper.rb
|