workflow-rails4 1.1.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 +21 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +663 -0
- data/Rakefile +31 -0
- data/gemfiles/Gemfile.rails-2.3.x +11 -0
- data/gemfiles/Gemfile.rails-3.x +11 -0
- data/gemfiles/Gemfile.rails-edge +12 -0
- data/lib/workflow.rb +277 -0
- data/lib/workflow/adapters/active_record.rb +66 -0
- data/lib/workflow/adapters/remodel.rb +15 -0
- data/lib/workflow/draw.rb +79 -0
- data/lib/workflow/errors.rb +18 -0
- data/lib/workflow/event.rb +18 -0
- data/lib/workflow/specification.rb +64 -0
- data/lib/workflow/state.rb +44 -0
- data/lib/workflow/version.rb +3 -0
- data/orders_workflow.png +0 -0
- data/test/active_record_scopes_test.rb +49 -0
- data/test/advanced_examples_test.rb +82 -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/inheritance_test.rb +60 -0
- data/test/main_test.rb +544 -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/readme_example.rb +37 -0
- data/test/test_helper.rb +39 -0
- data/test/without_active_record_test.rb +54 -0
- data/workflow.gemspec +32 -0
- metadata +202 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Workflow
|
2
|
+
class TransitionHalted < Exception
|
3
|
+
|
4
|
+
attr_reader :halted_because
|
5
|
+
|
6
|
+
def initialize(msg = nil)
|
7
|
+
@halted_because = msg
|
8
|
+
super msg
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
class NoTransitionAllowed < Exception; end
|
14
|
+
|
15
|
+
class WorkflowError < Exception; end
|
16
|
+
|
17
|
+
class WorkflowDefinitionError < Exception; end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Workflow
|
2
|
+
class Event
|
3
|
+
|
4
|
+
attr_accessor :name, :transitions_to, :meta, :action
|
5
|
+
|
6
|
+
def initialize(name, transitions_to, meta = {}, &action)
|
7
|
+
@name, @transitions_to, @meta, @action = name, transitions_to.to_sym, meta, action
|
8
|
+
end
|
9
|
+
|
10
|
+
def draw(graph, from_state)
|
11
|
+
graph.add_edges(from_state.name.to_s, transitions_to.to_s, meta.merge(:label => to_s))
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@name.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'workflow/state'
|
2
|
+
require 'workflow/event'
|
3
|
+
require 'workflow/errors'
|
4
|
+
|
5
|
+
module Workflow
|
6
|
+
class Specification
|
7
|
+
attr_accessor :states, :initial_state, :meta,
|
8
|
+
:on_transition_proc, :before_transition_proc, :after_transition_proc, :on_error_proc
|
9
|
+
|
10
|
+
def initialize(meta = {}, &specification)
|
11
|
+
@states = Hash.new
|
12
|
+
@meta = meta
|
13
|
+
instance_eval(&specification)
|
14
|
+
end
|
15
|
+
|
16
|
+
def state_names
|
17
|
+
states.keys
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def state(name, meta = {:meta => {}}, &events_and_etc)
|
23
|
+
# meta[:meta] to keep the API consistent..., gah
|
24
|
+
new_state = Workflow::State.new(name, self, meta[:meta])
|
25
|
+
@initial_state = new_state if @states.empty?
|
26
|
+
@states[name.to_sym] = new_state
|
27
|
+
@scoped_state = new_state
|
28
|
+
instance_eval(&events_and_etc) if events_and_etc
|
29
|
+
end
|
30
|
+
|
31
|
+
def event(name, args = {}, &action)
|
32
|
+
target = args[:transitions_to] || args[:transition_to]
|
33
|
+
raise WorkflowDefinitionError.new(
|
34
|
+
"missing ':transitions_to' in workflow event definition for '#{name}'") \
|
35
|
+
if target.nil?
|
36
|
+
@scoped_state.events[name.to_sym] =
|
37
|
+
Workflow::Event.new(name, target, (args[:meta] or {}), &action)
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_entry(&proc)
|
41
|
+
@scoped_state.on_entry = proc
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_exit(&proc)
|
45
|
+
@scoped_state.on_exit = proc
|
46
|
+
end
|
47
|
+
|
48
|
+
def after_transition(&proc)
|
49
|
+
@after_transition_proc = proc
|
50
|
+
end
|
51
|
+
|
52
|
+
def before_transition(&proc)
|
53
|
+
@before_transition_proc = proc
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_transition(&proc)
|
57
|
+
@on_transition_proc = proc
|
58
|
+
end
|
59
|
+
|
60
|
+
def on_error(&proc)
|
61
|
+
@on_error_proc = proc
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Workflow
|
2
|
+
class State
|
3
|
+
attr_accessor :name, :events, :meta, :on_entry, :on_exit
|
4
|
+
attr_reader :spec
|
5
|
+
|
6
|
+
def initialize(name, spec, meta = {})
|
7
|
+
@name, @spec, @events, @meta = name, spec, Hash.new, meta
|
8
|
+
end
|
9
|
+
|
10
|
+
def draw(graph)
|
11
|
+
defaults = {
|
12
|
+
:label => to_s,
|
13
|
+
:width => '1',
|
14
|
+
:height => '1',
|
15
|
+
:shape => 'ellipse'
|
16
|
+
}
|
17
|
+
|
18
|
+
node = graph.add_nodes(to_s, defaults.merge(meta))
|
19
|
+
|
20
|
+
# Add open arrow for initial state
|
21
|
+
# graph.add_edge(graph.add_node('starting_state', :shape => 'point'), node) if initial?
|
22
|
+
|
23
|
+
node
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
if RUBY_VERSION >= '1.9'
|
28
|
+
include Comparable
|
29
|
+
def <=>(other_state)
|
30
|
+
states = spec.states.keys
|
31
|
+
raise ArgumentError, "state `#{other_state}' does not exist" unless states.include?(other_state.to_sym)
|
32
|
+
states.index(self.to_sym) <=> states.index(other_state.to_sym)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"#{name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_sym
|
41
|
+
name.to_sym
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/orders_workflow.png
ADDED
Binary file
|
@@ -0,0 +1,49 @@
|
|
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 Article < ActiveRecord::Base
|
11
|
+
include Workflow
|
12
|
+
|
13
|
+
workflow do
|
14
|
+
state :new
|
15
|
+
state :accepted
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ActiveRecordScopesTest < ActiveRecordTestCase
|
20
|
+
|
21
|
+
def setup
|
22
|
+
super
|
23
|
+
|
24
|
+
ActiveRecord::Schema.define do
|
25
|
+
create_table :articles do |t|
|
26
|
+
t.string :title
|
27
|
+
t.string :body
|
28
|
+
t.string :blame_reason
|
29
|
+
t.string :reject_reason
|
30
|
+
t.string :workflow_state
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def assert_state(title, expected_state, klass = Order)
|
36
|
+
o = klass.find_by_title(title)
|
37
|
+
assert_equal expected_state, o.read_attribute(klass.workflow_column)
|
38
|
+
o
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'have "with_new_state" scope' do
|
42
|
+
assert_respond_to Article, :with_new_state
|
43
|
+
end
|
44
|
+
|
45
|
+
test 'have "with_accepted_state" scope' do
|
46
|
+
assert_respond_to Article, :with_accepted_state
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'workflow'
|
3
|
+
class AdvanceExamplesTest < ActiveRecordTestCase
|
4
|
+
|
5
|
+
class Article
|
6
|
+
include Workflow
|
7
|
+
workflow do
|
8
|
+
state :new do
|
9
|
+
event :submit, :transitions_to => :awaiting_review
|
10
|
+
end
|
11
|
+
state :awaiting_review do
|
12
|
+
event :review, :transitions_to => :being_reviewed
|
13
|
+
end
|
14
|
+
state :being_reviewed do
|
15
|
+
event :accept, :transitions_to => :accepted
|
16
|
+
event :reject, :transitions_to => :rejected
|
17
|
+
end
|
18
|
+
state :accepted do
|
19
|
+
end
|
20
|
+
state :rejected do
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
test '#63 undoing event - automatically add revert events for every defined event' do
|
26
|
+
# also see https://github.com/geekq/workflow/issues/63
|
27
|
+
spec = Article.workflow_spec
|
28
|
+
spec.state_names.each do |state_name|
|
29
|
+
state = spec.states[state_name]
|
30
|
+
|
31
|
+
(state.events.values.reject {|e| e.name.to_s =~ /^revert_/ }).each do |event|
|
32
|
+
event_name = event.name
|
33
|
+
revert_event_name = "revert_" + event_name.to_s
|
34
|
+
|
35
|
+
# Add revert events
|
36
|
+
spec.states[event.transitions_to.to_sym].events[revert_event_name.to_sym] =
|
37
|
+
Workflow::Event.new(revert_event_name, state, {})
|
38
|
+
|
39
|
+
# Add methods for revert events
|
40
|
+
Article.module_eval do
|
41
|
+
define_method "#{revert_event_name}!".to_sym do |*args|
|
42
|
+
process_event!(revert_event_name, *args)
|
43
|
+
end
|
44
|
+
define_method "can_#{revert_event_name}?" do
|
45
|
+
return self.current_state.events.include?(revert_event_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
a = Article.new
|
53
|
+
assert(a.new?, "should start with the 'new' state")
|
54
|
+
a.submit!
|
55
|
+
assert(a.awaiting_review?, "should now be in 'awaiting_review' state")
|
56
|
+
assert_equal(['revert_submit', 'review'], a.current_state.events.keys.map(&:to_s).sort)
|
57
|
+
a.revert_submit! # this method is added by our meta programming magic above
|
58
|
+
assert(a.new?, "should now be back in the 'new' state")
|
59
|
+
end
|
60
|
+
|
61
|
+
test '#92 Load workflow specification' do
|
62
|
+
c = Class.new
|
63
|
+
c.class_eval do
|
64
|
+
include Workflow
|
65
|
+
end
|
66
|
+
|
67
|
+
# build a Specification (you can load it from yaml file too)
|
68
|
+
myspec = Workflow::Specification.new do
|
69
|
+
state :one do
|
70
|
+
event :dynamic_transition, :transitions_to => :one_a
|
71
|
+
end
|
72
|
+
state :one_a
|
73
|
+
end
|
74
|
+
|
75
|
+
c.send :assign_workflow, myspec
|
76
|
+
|
77
|
+
a = c.new
|
78
|
+
a.dynamic_transition!(1)
|
79
|
+
assert a.one_a?, 'Expected successful transition to a new state'
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,119 @@
|
|
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
|
+
# Transition based validation
|
11
|
+
# ---------------------------
|
12
|
+
# If you are using ActiveRecord you might want to define different validations
|
13
|
+
# for different transitions. There is a `validates_presence_of` hook that let's
|
14
|
+
# you specify the attributes that need to be present for an successful transition.
|
15
|
+
# If the object is not valid at the end of the transition event the transition
|
16
|
+
# is halted and a TransitionHalted exception is thrown.
|
17
|
+
#
|
18
|
+
# Here is a sample that illustrates how to use the presence validation:
|
19
|
+
# (use case suggested by http://github.com/southdesign)
|
20
|
+
class Article < ActiveRecord::Base
|
21
|
+
include Workflow
|
22
|
+
workflow do
|
23
|
+
state :new do
|
24
|
+
event :accept, :transitions_to => :accepted, :meta => {:validates_presence_of => [:title, :body]}
|
25
|
+
event :reject, :transitions_to => :rejected
|
26
|
+
end
|
27
|
+
state :accepted do
|
28
|
+
event :blame, :transitions_to => :blamed, :meta => {:validates_presence_of => [:title, :body, :blame_reason]}
|
29
|
+
event :delete, :transitions_to => :deleted
|
30
|
+
end
|
31
|
+
state :rejected do
|
32
|
+
event :delete, :transitions_to => :deleted
|
33
|
+
end
|
34
|
+
state :blamed do
|
35
|
+
event :delete, :transitions_to => :deleted
|
36
|
+
end
|
37
|
+
state :deleted do
|
38
|
+
event :accept, :transitions_to => :accepted
|
39
|
+
end
|
40
|
+
|
41
|
+
on_transition do |from, to, triggering_event, *event_args|
|
42
|
+
if self.class.superclass.to_s.split("::").first == "ActiveRecord"
|
43
|
+
singleton = class << self; self end
|
44
|
+
validations = Proc.new {}
|
45
|
+
|
46
|
+
meta = Article.workflow_spec.states[from].events[triggering_event].meta
|
47
|
+
fields_to_validate = meta[:validates_presence_of]
|
48
|
+
if fields_to_validate
|
49
|
+
validations = Proc.new {
|
50
|
+
errors.add_on_blank(fields_to_validate) if fields_to_validate
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
singleton.send :define_method, :validate_for_transition, &validations
|
55
|
+
validate_for_transition
|
56
|
+
halt! "Event[#{triggering_event}]'s transitions_to[#{to}] is not valid." unless self.errors.empty?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class AdvancedHooksAndValidationTest < ActiveRecordTestCase
|
63
|
+
|
64
|
+
def setup
|
65
|
+
super
|
66
|
+
|
67
|
+
ActiveRecord::Schema.define do
|
68
|
+
create_table :articles do |t|
|
69
|
+
t.string :title
|
70
|
+
t.string :body
|
71
|
+
t.string :blame_reason
|
72
|
+
t.string :reject_reason
|
73
|
+
t.string :workflow_state
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
exec "INSERT INTO articles(title, body, blame_reason, reject_reason, workflow_state) VALUES('new1', NULL, NULL, NULL, 'new')"
|
78
|
+
exec "INSERT INTO articles(title, body, blame_reason, reject_reason, workflow_state) VALUES('new2', 'some content', NULL, NULL, 'new')"
|
79
|
+
exec "INSERT INTO articles(title, body, blame_reason, reject_reason, workflow_state) VALUES('accepted1', 'some content', NULL, NULL, 'accepted')"
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def assert_state(title, expected_state, klass = Order)
|
84
|
+
o = klass.find_by_title(title)
|
85
|
+
assert_equal expected_state, o.read_attribute(klass.workflow_column)
|
86
|
+
o
|
87
|
+
end
|
88
|
+
|
89
|
+
test 'deny transition from new to accepted because of the missing presence of the body' do
|
90
|
+
a = Article.find_by_title('new1');
|
91
|
+
assert_raise Workflow::TransitionHalted do
|
92
|
+
a.accept!
|
93
|
+
end
|
94
|
+
assert_state 'new1', 'new', Article
|
95
|
+
end
|
96
|
+
|
97
|
+
test 'allow transition from new to accepted because body is present this time' do
|
98
|
+
a = Article.find_by_title('new2');
|
99
|
+
assert a.accept!
|
100
|
+
assert_state 'new2', 'accepted', Article
|
101
|
+
end
|
102
|
+
|
103
|
+
test 'allow transition from accepted to blamed because of a blame_reason' do
|
104
|
+
a = Article.find_by_title('accepted1');
|
105
|
+
a.blame_reason = "Provocant thesis"
|
106
|
+
assert a.blame!
|
107
|
+
assert_state 'accepted1', 'blamed', Article
|
108
|
+
end
|
109
|
+
|
110
|
+
test 'deny transition from accepted to blamed because of no blame_reason' do
|
111
|
+
a = Article.find_by_title('accepted1');
|
112
|
+
assert_raise Workflow::TransitionHalted do
|
113
|
+
assert a.blame!
|
114
|
+
end
|
115
|
+
assert_state 'accepted1', 'accepted', Article
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
@@ -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)
|
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)
|
68
|
+
o.update_attribute :workflow_state, 'some_overridden_value'
|
69
|
+
assert_equal 'some_overridden_value', o.read_attribute(:workflow_state)
|
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
|
+
|