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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +36 -0
  4. data/CHANGELOG.md +133 -0
  5. data/Gemfile +3 -0
  6. data/MIT-LICENSE +22 -0
  7. data/README.md +707 -0
  8. data/Rakefile +30 -0
  9. data/gemfiles/Gemfile.rails-3.x +12 -0
  10. data/gemfiles/Gemfile.rails-4.0 +14 -0
  11. data/gemfiles/Gemfile.rails-4.1 +14 -0
  12. data/gemfiles/Gemfile.rails-4.2 +14 -0
  13. data/gemfiles/Gemfile.rails-edge +14 -0
  14. data/lib/workflow/adapters/active_record.rb +75 -0
  15. data/lib/workflow/adapters/remodel.rb +15 -0
  16. data/lib/workflow/draw.rb +79 -0
  17. data/lib/workflow/errors.rb +20 -0
  18. data/lib/workflow/event.rb +38 -0
  19. data/lib/workflow/event_collection.rb +36 -0
  20. data/lib/workflow/specification.rb +83 -0
  21. data/lib/workflow/state.rb +44 -0
  22. data/lib/workflow/version.rb +3 -0
  23. data/lib/workflow.rb +307 -0
  24. data/orders_workflow.png +0 -0
  25. data/test/active_record_scopes_test.rb +56 -0
  26. data/test/active_record_scopes_with_values_test.rb +79 -0
  27. data/test/adapter_hook_test.rb +52 -0
  28. data/test/advanced_examples_test.rb +84 -0
  29. data/test/advanced_hooks_and_validation_test.rb +119 -0
  30. data/test/attr_protected_test.rb +107 -0
  31. data/test/before_transition_test.rb +36 -0
  32. data/test/couchtiny_example.rb +46 -0
  33. data/test/enum_values_in_memory_test.rb +23 -0
  34. data/test/enum_values_test.rb +30 -0
  35. data/test/incline_column_test.rb +54 -0
  36. data/test/inheritance_test.rb +56 -0
  37. data/test/main_test.rb +588 -0
  38. data/test/multiple_workflows_test.rb +84 -0
  39. data/test/new_versions/compare_states_test.rb +32 -0
  40. data/test/new_versions/persistence_test.rb +62 -0
  41. data/test/on_error_test.rb +52 -0
  42. data/test/on_unavailable_transition_test.rb +85 -0
  43. data/test/readme_example.rb +37 -0
  44. data/test/test_helper.rb +39 -0
  45. data/test/without_active_record_test.rb +54 -0
  46. data/workflow-orchestrator.gemspec +42 -0
  47. 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