workflow-orchestrator 1.3.0
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.travis.yml +36 -0
- data/CHANGELOG.md +133 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +22 -0
- data/README.md +707 -0
- data/Rakefile +30 -0
- data/gemfiles/Gemfile.rails-3.x +12 -0
- data/gemfiles/Gemfile.rails-4.0 +14 -0
- data/gemfiles/Gemfile.rails-4.1 +14 -0
- data/gemfiles/Gemfile.rails-4.2 +14 -0
- data/gemfiles/Gemfile.rails-edge +14 -0
- data/lib/workflow/adapters/active_record.rb +75 -0
- data/lib/workflow/adapters/remodel.rb +15 -0
- data/lib/workflow/draw.rb +79 -0
- data/lib/workflow/errors.rb +20 -0
- data/lib/workflow/event.rb +38 -0
- data/lib/workflow/event_collection.rb +36 -0
- data/lib/workflow/specification.rb +83 -0
- data/lib/workflow/state.rb +44 -0
- data/lib/workflow/version.rb +3 -0
- data/lib/workflow.rb +307 -0
- data/orders_workflow.png +0 -0
- data/test/active_record_scopes_test.rb +56 -0
- data/test/active_record_scopes_with_values_test.rb +79 -0
- data/test/adapter_hook_test.rb +52 -0
- data/test/advanced_examples_test.rb +84 -0
- data/test/advanced_hooks_and_validation_test.rb +119 -0
- data/test/attr_protected_test.rb +107 -0
- data/test/before_transition_test.rb +36 -0
- data/test/couchtiny_example.rb +46 -0
- data/test/enum_values_in_memory_test.rb +23 -0
- data/test/enum_values_test.rb +30 -0
- data/test/incline_column_test.rb +54 -0
- data/test/inheritance_test.rb +56 -0
- data/test/main_test.rb +588 -0
- data/test/multiple_workflows_test.rb +84 -0
- data/test/new_versions/compare_states_test.rb +32 -0
- data/test/new_versions/persistence_test.rb +62 -0
- data/test/on_error_test.rb +52 -0
- data/test/on_unavailable_transition_test.rb +85 -0
- data/test/readme_example.rb +37 -0
- data/test/test_helper.rb +39 -0
- data/test/without_active_record_test.rb +54 -0
- data/workflow-orchestrator.gemspec +42 -0
- metadata +267 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
$VERBOSE = false
|
4
|
+
require 'active_record'
|
5
|
+
require 'logger'
|
6
|
+
require 'sqlite3'
|
7
|
+
require 'workflow'
|
8
|
+
require 'mocha/setup'
|
9
|
+
require 'stringio'
|
10
|
+
require 'protected_attributes' if ActiveRecord::VERSION::MAJOR >= 4
|
11
|
+
|
12
|
+
ActiveRecord::Migration.verbose = false
|
13
|
+
|
14
|
+
class AttrProtectedTestOrder < ActiveRecord::Base
|
15
|
+
include Workflow
|
16
|
+
|
17
|
+
workflow do
|
18
|
+
state :submitted do
|
19
|
+
event :accept, :transitions_to => :accepted, :meta => {:doc_weight => 8} do |reviewer, args|
|
20
|
+
end
|
21
|
+
end
|
22
|
+
state :accepted do
|
23
|
+
event :ship, :transitions_to => :shipped
|
24
|
+
end
|
25
|
+
state :shipped
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessible :title # protecting all the other attributes
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
AttrProtectedTestOrder.logger = Logger.new(STDOUT) # active_record 2.3 expects a logger instance
|
33
|
+
AttrProtectedTestOrder.logger.level = Logger::WARN # switch to Logger::DEBUG to see the SQL statements
|
34
|
+
|
35
|
+
class AttrProtectedTest < ActiveRecordTestCase
|
36
|
+
|
37
|
+
def setup
|
38
|
+
super
|
39
|
+
|
40
|
+
ActiveRecord::Schema.define do
|
41
|
+
create_table :attr_protected_test_orders do |t|
|
42
|
+
t.string :title, :null => false
|
43
|
+
t.string :workflow_state
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
exec "INSERT INTO attr_protected_test_orders(title, workflow_state) VALUES('order1', 'submitted')"
|
48
|
+
exec "INSERT INTO attr_protected_test_orders(title, workflow_state) VALUES('order2', 'accepted')"
|
49
|
+
exec "INSERT INTO attr_protected_test_orders(title, workflow_state) VALUES('order3', 'accepted')"
|
50
|
+
exec "INSERT INTO attr_protected_test_orders(title, workflow_state) VALUES('order4', 'accepted')"
|
51
|
+
exec "INSERT INTO attr_protected_test_orders(title, workflow_state) VALUES('order5', 'accepted')"
|
52
|
+
exec "INSERT INTO attr_protected_test_orders(title, workflow_state) VALUES('protected order', 'submitted')"
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_state(title, expected_state, klass = AttrProtectedTestOrder)
|
56
|
+
o = klass.find_by_title(title)
|
57
|
+
assert_equal expected_state, o.read_attribute(klass.workflow_column)
|
58
|
+
o
|
59
|
+
end
|
60
|
+
|
61
|
+
test 'cannot mass-assign workflow_state if attr_protected' do
|
62
|
+
o = AttrProtectedTestOrder.find_by_title('order1')
|
63
|
+
assert_equal 'submitted', o.read_attribute(:workflow_state).to_s
|
64
|
+
AttrProtectedTestOrder.logger.level = Logger::ERROR # ignore warnings
|
65
|
+
o.update_attributes :workflow_state => 'some_bad_value'
|
66
|
+
AttrProtectedTestOrder.logger.level = Logger::WARN
|
67
|
+
assert_equal 'submitted', o.read_attribute(:workflow_state).to_s
|
68
|
+
o.update_attribute :workflow_state, 'some_overridden_value'
|
69
|
+
assert_equal 'some_overridden_value', o.read_attribute(:workflow_state).to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
test 'immediately save the new workflow_state on state machine transition' do
|
73
|
+
o = assert_state 'order2', 'accepted'
|
74
|
+
assert o.ship!
|
75
|
+
assert_state 'order2', 'shipped'
|
76
|
+
end
|
77
|
+
|
78
|
+
test 'persist workflow_state in the db and reload' do
|
79
|
+
o = assert_state 'order3', 'accepted'
|
80
|
+
assert_equal :accepted, o.current_state.name
|
81
|
+
o.ship! # should save in the database, no `o.save!` needed
|
82
|
+
|
83
|
+
assert_state 'order3', 'shipped'
|
84
|
+
|
85
|
+
o.reload
|
86
|
+
assert_equal 'shipped', o.read_attribute(:workflow_state)
|
87
|
+
end
|
88
|
+
|
89
|
+
test 'default workflow column should be workflow_state' do
|
90
|
+
o = assert_state 'order4', 'accepted'
|
91
|
+
assert_equal :workflow_state, o.class.workflow_column
|
92
|
+
end
|
93
|
+
|
94
|
+
test 'access workflow specification' do
|
95
|
+
assert_equal 3, AttrProtectedTestOrder.workflow_spec.states.length
|
96
|
+
assert_equal ['submitted', 'accepted', 'shipped'].sort,
|
97
|
+
AttrProtectedTestOrder.workflow_spec.state_names.map{|n| n.to_s}.sort
|
98
|
+
end
|
99
|
+
|
100
|
+
test 'current state object' do
|
101
|
+
o = assert_state 'order5', 'accepted'
|
102
|
+
assert_equal 'accepted', o.current_state.to_s
|
103
|
+
assert_equal 1, o.current_state.events.length
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'workflow'
|
3
|
+
|
4
|
+
class BeforeTransitionTest < Test::Unit::TestCase
|
5
|
+
class MyFlow
|
6
|
+
attr_reader :history
|
7
|
+
def initialize
|
8
|
+
@history = []
|
9
|
+
end
|
10
|
+
|
11
|
+
include Workflow
|
12
|
+
workflow do
|
13
|
+
state :first do
|
14
|
+
event :forward, :transitions_to => :second do
|
15
|
+
@history << 'forward'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
state :second do
|
19
|
+
event :back, :transitions_to => :first do
|
20
|
+
@history << 'back'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
before_transition { @history << 'before' }
|
25
|
+
after_transition { @history << 'after' }
|
26
|
+
on_transition { @history << 'on' }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'that before_transition is run before the action' do
|
31
|
+
flow = MyFlow.new
|
32
|
+
flow.forward!
|
33
|
+
flow.back!
|
34
|
+
assert flow.history == ['before', 'forward', 'on', 'after', 'before', 'back', 'on', 'after']
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'couchtiny'
|
3
|
+
require 'couchtiny/document'
|
4
|
+
require 'workflow'
|
5
|
+
|
6
|
+
class User < CouchTiny::Document
|
7
|
+
include Workflow
|
8
|
+
workflow do
|
9
|
+
state :submitted do
|
10
|
+
event :activate_via_link, :transitions_to => :proved_email
|
11
|
+
end
|
12
|
+
state :proved_email
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_workflow_state
|
16
|
+
self[:workflow_state]
|
17
|
+
end
|
18
|
+
|
19
|
+
def persist_workflow_state(new_value)
|
20
|
+
self[:workflow_state] = new_value
|
21
|
+
save!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
class CouchtinyExample < Test::Unit::TestCase
|
27
|
+
|
28
|
+
def setup
|
29
|
+
db = CouchTiny::Database.url("http://127.0.0.1:5984/test-workflow")
|
30
|
+
db.delete_database! rescue nil
|
31
|
+
db.create_database!
|
32
|
+
User.use_database db
|
33
|
+
end
|
34
|
+
|
35
|
+
test 'CouchDB persistence' do
|
36
|
+
user = User.new :email => 'manya@example.com'
|
37
|
+
user.save!
|
38
|
+
assert user.submitted?
|
39
|
+
user.activate_via_link!
|
40
|
+
assert user.proved_email?
|
41
|
+
|
42
|
+
reloaded_user = User.get user.id
|
43
|
+
puts reloaded_user.inspect
|
44
|
+
assert reloaded_user.proved_email?, 'Reloaded user should have the desired workflow state'
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class EnumValuesInMemoryTest < ActiveRecordTestCase
|
2
|
+
test 'that the value of the state is persisted instead of the name' do
|
3
|
+
|
4
|
+
class EnumFlow
|
5
|
+
attr_reader :workflow_state
|
6
|
+
include Workflow
|
7
|
+
|
8
|
+
workflow do
|
9
|
+
state :first, 1 do
|
10
|
+
event :forward, :transitions_to => :second
|
11
|
+
end
|
12
|
+
state :second, 2 do
|
13
|
+
event :back, :transitions_to => :first
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
flow = EnumFlow.new
|
19
|
+
flow.forward!
|
20
|
+
assert flow.workflow_state == 2
|
21
|
+
assert flow.second?
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'workflow'
|
3
|
+
|
4
|
+
class EnumValuesTest < ActiveRecordTestCase
|
5
|
+
test 'that the value of the state is persisted instead of the name' do
|
6
|
+
|
7
|
+
ActiveRecord::Schema.define do
|
8
|
+
create_table(:active_enum_flows) { |t| t.integer :state }
|
9
|
+
end
|
10
|
+
|
11
|
+
class ActiveEnumFlow < ActiveRecord::Base
|
12
|
+
include Workflow
|
13
|
+
workflow_column :state
|
14
|
+
|
15
|
+
workflow do
|
16
|
+
state :first, 1 do
|
17
|
+
event :forward, :transitions_to => :second
|
18
|
+
end
|
19
|
+
state :second, 2 do
|
20
|
+
event :back, :transitions_to => :first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
flow = ActiveEnumFlow.create
|
26
|
+
flow.forward!
|
27
|
+
assert flow.state == 2
|
28
|
+
assert flow.second?
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
$VERBOSE = false
|
4
|
+
require 'active_record'
|
5
|
+
require 'sqlite3'
|
6
|
+
require 'workflow'
|
7
|
+
|
8
|
+
ActiveRecord::Migration.verbose = false
|
9
|
+
|
10
|
+
class EnumArticleHi < ActiveRecord::Base
|
11
|
+
include Workflow
|
12
|
+
|
13
|
+
workflow :inline_column_hi do
|
14
|
+
state :new, 1 do
|
15
|
+
event :accept, transitions_to: :accepted
|
16
|
+
end
|
17
|
+
state :accepted, 3
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class InlineColumnTest < ActiveRecordTestCase
|
22
|
+
|
23
|
+
def setup
|
24
|
+
super
|
25
|
+
|
26
|
+
ActiveRecord::Schema.define do
|
27
|
+
create_table :enum_article_his do |t|
|
28
|
+
t.string :title
|
29
|
+
t.string :body
|
30
|
+
t.string :blame_reason
|
31
|
+
t.string :reject_reason
|
32
|
+
t.integer :inline_column_hi
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
test '"with_new_state" selects matching value' do
|
38
|
+
article = EnumArticleHi.create
|
39
|
+
assert_equal(article.inline_column_hi, 1)
|
40
|
+
article.accept!
|
41
|
+
assert_equal(article.inline_column_hi, 3)
|
42
|
+
end
|
43
|
+
|
44
|
+
test 'allows passing in state value on create' do
|
45
|
+
article = EnumArticleHi.create(inline_column_hi: 3)
|
46
|
+
assert_equal(article.inline_column_hi, 3)
|
47
|
+
end
|
48
|
+
|
49
|
+
test 'allows passing in state name on create' do
|
50
|
+
article = EnumArticleHi.create(inline_column_hi: :accepted)
|
51
|
+
assert_equal(article.inline_column_hi, 3)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'workflow'
|
3
|
+
class InheritanceTest < ActiveRecordTestCase
|
4
|
+
|
5
|
+
test '#69 inheritance' do
|
6
|
+
class Animal
|
7
|
+
include Workflow
|
8
|
+
|
9
|
+
workflow do
|
10
|
+
|
11
|
+
state :conceived do
|
12
|
+
event :birth, :transition_to => :born
|
13
|
+
end
|
14
|
+
|
15
|
+
state :born do
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Cat < Animal
|
22
|
+
include Workflow
|
23
|
+
workflow do
|
24
|
+
|
25
|
+
state :upset do
|
26
|
+
event :scratch, :transition_to => :hiding
|
27
|
+
end
|
28
|
+
|
29
|
+
state :hiding do
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
assert_equal [:born, :conceived], Animal.workflow_spec.states.keys.sort
|
36
|
+
assert_equal [:hiding, :upset], Cat.workflow_spec.states.keys.sort, 'Workflow definitions are not inherited'
|
37
|
+
|
38
|
+
animal = Animal.new
|
39
|
+
cat = Cat.new
|
40
|
+
|
41
|
+
animal.birth!
|
42
|
+
|
43
|
+
assert_raise NoMethodError, 'Methods defined by the old workflow spec should have be gone away' do
|
44
|
+
cat.birth!
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_equal [:birth!, :halt!, :process_event!], bang_methods(animal)
|
48
|
+
assert_equal [:halt!, :process_event!, :scratch!], bang_methods(cat)
|
49
|
+
end
|
50
|
+
|
51
|
+
def bang_methods(obj)
|
52
|
+
non_trivial_methods = obj.public_methods - Object.public_methods
|
53
|
+
methods_with_bang = non_trivial_methods.select { |m| m =~ /!$/ }
|
54
|
+
methods_with_bang.map(&:to_sym).sort
|
55
|
+
end
|
56
|
+
end
|