golem_statemachine 0.9
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 +3 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +472 -0
- data/Rakefile +23 -0
- data/examples/document.rb +44 -0
- data/examples/monster.rb +100 -0
- data/examples/seminar.rb +138 -0
- data/examples/seminar_enrollment.rb +141 -0
- data/init.rb +5 -0
- data/install.rb +1 -0
- data/lib/golem.rb +207 -0
- data/lib/golem/dsl/decision_def.rb +38 -0
- data/lib/golem/dsl/event_def.rb +89 -0
- data/lib/golem/dsl/state_def.rb +34 -0
- data/lib/golem/dsl/state_machine_def.rb +73 -0
- data/lib/golem/dsl/transition_def.rb +51 -0
- data/lib/golem/model/callback.rb +32 -0
- data/lib/golem/model/condition.rb +12 -0
- data/lib/golem/model/event.rb +12 -0
- data/lib/golem/model/state.rb +26 -0
- data/lib/golem/model/state_machine.rb +224 -0
- data/lib/golem/model/transition.rb +37 -0
- data/lib/golem/util/element_collection.rb +33 -0
- data/tasks/golem_statemachine_tasks.rake +4 -0
- data/test/active_record_test.rb +189 -0
- data/test/dsl_test.rb +429 -0
- data/test/monster_test.rb +110 -0
- data/test/problematic_test.rb +95 -0
- data/test/seminar_test.rb +106 -0
- data/test/statemachine_assertions.rb +79 -0
- data/test/test_helper.rb +5 -0
- data/uninstall.rb +1 -0
- metadata +119 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'golem/model/condition'
|
2
|
+
require 'golem/model/callback'
|
3
|
+
|
4
|
+
module Golem
|
5
|
+
module DSL
|
6
|
+
class TransitionDef
|
7
|
+
def initialize(machine, event, from_state, options = {}, &block)
|
8
|
+
@machine = machine
|
9
|
+
@event = event
|
10
|
+
@from = from_state
|
11
|
+
|
12
|
+
if options[:to].blank? || options[:to] == :self
|
13
|
+
@to = options[:to] = @state
|
14
|
+
else
|
15
|
+
@to = @machine.get_or_define_state(options[:to])
|
16
|
+
end
|
17
|
+
|
18
|
+
callbacks = {}
|
19
|
+
callbacks[:on_transition] = options[:action] if options[:action]
|
20
|
+
|
21
|
+
@transition = Golem::Model::Transition.new(@from, @to || @from, options[:guards], callbacks)
|
22
|
+
|
23
|
+
instance_eval(&block) if block
|
24
|
+
|
25
|
+
@from.transitions_on_event[@event] ||= []
|
26
|
+
@from.transitions_on_event[@event] << @transition
|
27
|
+
end
|
28
|
+
|
29
|
+
def guard(callback_or_options = {}, guard_options = {}, &block)
|
30
|
+
if callback_or_options.kind_of? Hash
|
31
|
+
callback = block
|
32
|
+
guard_options = callback_or_options
|
33
|
+
else
|
34
|
+
callback = callback_or_options
|
35
|
+
end
|
36
|
+
|
37
|
+
@transition.guards << Golem::Model::Condition.new(callback, guard_options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def action(callback = nil, &block)
|
41
|
+
#if @transition.callbacks[:on_transition]
|
42
|
+
# puts "WARNING: Overriding event action for #{@transition.to_s.inspect}."
|
43
|
+
#end
|
44
|
+
|
45
|
+
callback = block unless callback
|
46
|
+
|
47
|
+
@transition.callbacks[:on_transition] = Golem::Model::Callback.new(callback)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Golem
|
2
|
+
module Model
|
3
|
+
class Callback
|
4
|
+
attr_accessor :callback
|
5
|
+
|
6
|
+
def initialize(callback, options = {})
|
7
|
+
@callback = callback
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(obj, *args)
|
11
|
+
case @callback
|
12
|
+
when Proc
|
13
|
+
if @callback.arity.abs > 1
|
14
|
+
@callback.call(obj, *args)
|
15
|
+
else
|
16
|
+
@callback.call(obj)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
if obj.method(@callback).arity.abs > 0
|
20
|
+
obj.send(@callback, *args)
|
21
|
+
else
|
22
|
+
obj.send(@callback)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"#{@callback.inspect}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'golem/util/element_collection'
|
2
|
+
|
3
|
+
module Golem
|
4
|
+
module Model
|
5
|
+
class State
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :callbacks
|
8
|
+
attr_reader :transitions_on_event
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
name = name.to_sym unless name.is_a?(Symbol)
|
12
|
+
@name = name
|
13
|
+
@transitions_on_event = Golem::Util::ElementCollection.new
|
14
|
+
@callbacks = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
name.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_sym
|
22
|
+
name.to_sym
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'golem/model/event'
|
2
|
+
require 'golem/model/state'
|
3
|
+
require 'golem/model/transition'
|
4
|
+
require 'golem/util/element_collection'
|
5
|
+
|
6
|
+
module Golem
|
7
|
+
|
8
|
+
module Model
|
9
|
+
|
10
|
+
class StateMachine
|
11
|
+
attr_accessor :name
|
12
|
+
attr_accessor :state_attribute
|
13
|
+
attr_reader :states
|
14
|
+
attr_reader :events
|
15
|
+
attr_accessor :transition_errors
|
16
|
+
|
17
|
+
# Callback executed on every successful transition.
|
18
|
+
attr_accessor :on_all_transitions
|
19
|
+
|
20
|
+
# Callback executed on every successful event.
|
21
|
+
attr_accessor :on_all_events
|
22
|
+
|
23
|
+
def initialize(name)
|
24
|
+
@name = name
|
25
|
+
@states = Golem::Util::ElementCollection.new(Golem::Model::State)
|
26
|
+
@events = Golem::Util::ElementCollection.new(Golem::Model::Event)
|
27
|
+
@transition_errors = []
|
28
|
+
@throw_exceptions = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def initial_state
|
32
|
+
@initial_state
|
33
|
+
end
|
34
|
+
|
35
|
+
def initial_state=(state)
|
36
|
+
# for the sake of readability in debugging, we store initial state by name rather than by reference to a State object
|
37
|
+
@initial_state = state.name
|
38
|
+
end
|
39
|
+
|
40
|
+
def all_states
|
41
|
+
@states
|
42
|
+
end
|
43
|
+
|
44
|
+
def all_events
|
45
|
+
@events
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_current_state_of(obj)
|
49
|
+
obj.send(state_attribute)
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_current_state_of(obj, state)
|
53
|
+
obj.send("#{state_attribute}=".to_sym, state)
|
54
|
+
end
|
55
|
+
|
56
|
+
def init(obj, *args)
|
57
|
+
# set the initial state
|
58
|
+
set_current_state_of(obj, initial_state)
|
59
|
+
|
60
|
+
# call the on_entry callback for the initial state (if defined)
|
61
|
+
init_state = states[get_current_state_of(obj)]
|
62
|
+
init_state.callbacks[:on_enter].call(obj, *args) if init_state && init_state.callbacks[:on_enter]
|
63
|
+
end
|
64
|
+
|
65
|
+
def fire_event_with_exceptions(obj, event, *args)
|
66
|
+
@throw_exceptions = true
|
67
|
+
fire_event(obj, event, *args)
|
68
|
+
end
|
69
|
+
|
70
|
+
def fire_event_without_exceptions(obj, event, *args)
|
71
|
+
@throw_exceptions = false
|
72
|
+
fire_event(obj, event, *args)
|
73
|
+
end
|
74
|
+
|
75
|
+
def fire_event(obj, event, *args)
|
76
|
+
@transition_errors = []
|
77
|
+
transition = determine_transition_on_event(obj, event, *args)
|
78
|
+
|
79
|
+
on_all_events.call(obj, event, args) if on_all_events
|
80
|
+
|
81
|
+
if transition
|
82
|
+
before_state = states[get_current_state_of(obj)]
|
83
|
+
before_state.callbacks[:on_exit].call(obj, *args) if before_state.callbacks[:on_exit]
|
84
|
+
|
85
|
+
set_current_state_of(obj, transition.to.name)
|
86
|
+
transition.callbacks[:on_transition].call(obj, *args) if transition.callbacks[:on_transition]
|
87
|
+
on_all_transitions.call(obj, event, transition, *args) if on_all_transitions
|
88
|
+
|
89
|
+
after_state = states[get_current_state_of(obj)]
|
90
|
+
after_state.callbacks[:on_enter].call(obj, *args) if after_state.callbacks[:on_enter]
|
91
|
+
|
92
|
+
save_result = true
|
93
|
+
if @throw_exceptions
|
94
|
+
save_result = obj.save! if obj.respond_to?(:save!)
|
95
|
+
else
|
96
|
+
save_result = obj.save if obj.respond_to?(:save)
|
97
|
+
end
|
98
|
+
return save_result
|
99
|
+
else
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def determine_transition_on_event(obj, event, *args)
|
105
|
+
event = @events[event] unless event.is_a?(Golem::Model::Event)
|
106
|
+
|
107
|
+
from_state = states[get_current_state_of(obj)]
|
108
|
+
possible_transitions = from_state.transitions_on_event[event.name]
|
109
|
+
|
110
|
+
selected_transition = determine_transition(possible_transitions, obj, *args)
|
111
|
+
|
112
|
+
if selected_transition.nil?
|
113
|
+
if @cannot_transition_because.blank?
|
114
|
+
msg = "#{event.name.to_s.inspect} is not a valid action for #{obj} because no outgoing transitions are available when #{name.blank? ? "the state" : "#{name} "} is #{from_state}."
|
115
|
+
msg << "\n\tPossible transitions are: \n\t\t#{possible_transitions.collect.collect{|t| t.to_s}.join("\n\t\t")}" unless possible_transitions.blank?
|
116
|
+
elsif @cannot_transition_because.length == 1
|
117
|
+
msg = "#{event.name.to_s.inspect} is not a valid action for #{obj} because #{@cannot_transition_because.first}."
|
118
|
+
else
|
119
|
+
msg = "#{event.name.to_s.inspect} is not a valid action for #{obj} because #{@cannot_transition_because.uniq.join(" and ")}"
|
120
|
+
end
|
121
|
+
|
122
|
+
if @throw_exceptions
|
123
|
+
raise Golem::ImpossibleEvent.new(msg, event, obj, @cannot_transition_because)
|
124
|
+
else
|
125
|
+
obj.transition_errors << msg
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
return selected_transition
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_or_define_state(state)
|
133
|
+
if states[state]
|
134
|
+
return states[state]
|
135
|
+
else
|
136
|
+
case state
|
137
|
+
when Golem::Model::State
|
138
|
+
return states[state] = state
|
139
|
+
when String, Symbol
|
140
|
+
return states[state] = Golem::Model::State.new(state)
|
141
|
+
else
|
142
|
+
raise ArgumentError, "State must be a Golem::Model::State, String, or Symbol but is a #{state.class}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def get_or_define_event(event)
|
148
|
+
if events[event]
|
149
|
+
return events[event]
|
150
|
+
else
|
151
|
+
case event
|
152
|
+
when Golem::Model::Event
|
153
|
+
return events[event] = event
|
154
|
+
when String, Symbol
|
155
|
+
return events[event] = Golem::Model::Event.new(event)
|
156
|
+
else
|
157
|
+
raise ArgumentError, "Event must be a Golem::Model::Event, String, or Symbol but is a #{event.class}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# def get_or_create_state(name, options = {})
|
163
|
+
# @states[name] || define_state(name, options)
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# def define_state(name, options = {})
|
167
|
+
#
|
168
|
+
# s.set_callback(:on_enter, options[:enter]) if options[:enter]
|
169
|
+
# s.set_callback(:on_exit, options[:exit]) if options[:exit]
|
170
|
+
# # TODO: implement :do callback as an "Activity" (Thread/subrocess?)
|
171
|
+
# states[name] = s
|
172
|
+
# return s
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# def get_or_define_event(name, options = {})
|
176
|
+
# @events[name] || define_event(name, options)
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# def define_event(name, options = {}, &block)
|
180
|
+
# e = Event.new(name)
|
181
|
+
# e.instance_eval(&block) if block
|
182
|
+
#
|
183
|
+
# model.class_eval do
|
184
|
+
# define_method("#{name}!") do |*args|
|
185
|
+
# model.transaction do
|
186
|
+
# e.fire(args)
|
187
|
+
# end
|
188
|
+
# end
|
189
|
+
# end
|
190
|
+
#
|
191
|
+
# @events << e
|
192
|
+
# return e
|
193
|
+
# end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def determine_transition(possible_transitions, obj, *args)
|
198
|
+
return nil if possible_transitions.blank?
|
199
|
+
|
200
|
+
@cannot_transition_because = []
|
201
|
+
|
202
|
+
possible_transitions.each do |transition|
|
203
|
+
guard_failed = false
|
204
|
+
unless transition.guards.empty?
|
205
|
+
transition.guards.each do |guard| # all guards must evaluate to true
|
206
|
+
unless guard.call(obj, *args)
|
207
|
+
@cannot_transition_because << (guard.failure_message || "#{guard} is false")
|
208
|
+
guard_failed = true
|
209
|
+
break
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
next if guard_failed
|
215
|
+
|
216
|
+
return transition
|
217
|
+
end
|
218
|
+
|
219
|
+
return nil
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# To change this template, choose Tools | Templates
|
2
|
+
# and open the template in the editor.
|
3
|
+
|
4
|
+
module Golem
|
5
|
+
module Model
|
6
|
+
class Transition
|
7
|
+
attr_reader :from
|
8
|
+
attr_reader :to
|
9
|
+
attr_accessor :guards
|
10
|
+
attr_accessor :callbacks
|
11
|
+
|
12
|
+
def initialize(from, to, guards = [], callbacks = {})
|
13
|
+
@from = from
|
14
|
+
@to = to
|
15
|
+
|
16
|
+
raise ArgumentError, "'guards' must be an Enumerable collection of Golem::Model::Conditions but is #{guards.inspect}" unless
|
17
|
+
guards.blank? || (guards.kind_of?(Enumerable) && guards.all?{|g| g.kind_of?(Golem::Model::Condition)})
|
18
|
+
|
19
|
+
@guards = guards
|
20
|
+
|
21
|
+
raise ArgumentError, "'callbacks' must be a Hash of Golem::Model::Callbacks but is #{callbacks.inspect}" unless
|
22
|
+
callbacks.blank? || (callbacks.kind_of?(Hash) && callbacks.all?{|k, c| c.kind_of?(Golem::Model::Callback)})
|
23
|
+
|
24
|
+
# only the :on_transition callback is currently implemented, but using a Hash of callbacks here leaves open
|
25
|
+
# the possibility of other callbacks (e.g. :on_start, :on_finish, etc.)
|
26
|
+
@callbacks = callbacks
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
s ="Transition from #{from} to #{to}"
|
31
|
+
s << " [#{guards.collect{|g| g.to_s}.join(" and ")}]" unless guards.empty?
|
32
|
+
s << " / #{callbacks.collect{|k,v| v.to_s}.join(",")}" unless callbacks.blank? || callbacks.values.all?{|v| v.blank?}
|
33
|
+
return s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Golem
|
2
|
+
module Util
|
3
|
+
class ElementCollection
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(restricted_to_type = nil)
|
7
|
+
@collection = {}
|
8
|
+
@restricted_to_type = restricted_to_type
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
return nil if key.nil?
|
13
|
+
key = key.name if key.respond_to?(:name)
|
14
|
+
@collection[key.to_sym]
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
key = key.name if key.respond_to?(:name)
|
19
|
+
raise ArgumentError, "Value must be a #{@restricted_to_type.name.inspect} but is a #{value.class.name.inspect}!" if
|
20
|
+
@restricted_to_type && !value.kind_of?(@restricted_to_type)
|
21
|
+
@collection[key.to_sym] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def each
|
25
|
+
@collection.values.each{|v| yield v}
|
26
|
+
end
|
27
|
+
|
28
|
+
def values
|
29
|
+
@collection.values
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'sqlite3'
|
8
|
+
rescue
|
9
|
+
gem 'sqlite3-ruby'
|
10
|
+
require 'sqlite3'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'activerecord'
|
14
|
+
|
15
|
+
require 'ruby-debug'
|
16
|
+
|
17
|
+
class ActiveRecordTest < Test::Unit::TestCase
|
18
|
+
|
19
|
+
def setup
|
20
|
+
eval %{
|
21
|
+
class Foo < ActiveRecord::Base
|
22
|
+
include Golem
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
File.delete('test.db') if File.exists?('test.db')
|
27
|
+
|
28
|
+
ActiveRecord::Base.establish_connection(
|
29
|
+
:adapter => 'sqlite3',
|
30
|
+
:database => 'test.db'
|
31
|
+
)
|
32
|
+
|
33
|
+
tmp = $stdout
|
34
|
+
$stdout = StringIO.new
|
35
|
+
ActiveRecord::Schema.define do
|
36
|
+
create_table :foos do |t|
|
37
|
+
t.column :state, :string, :null => true
|
38
|
+
t.column :alpha_state, :string, :null => true
|
39
|
+
t.column :status, :string, :null => true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
$stdout = tmp
|
43
|
+
end
|
44
|
+
|
45
|
+
def teardown
|
46
|
+
self.class.send(:remove_const, :Foo)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_restore_state
|
50
|
+
foo = Foo.create(
|
51
|
+
:state => 'b',
|
52
|
+
:alpha_state => 'c',
|
53
|
+
:status => 'd'
|
54
|
+
)
|
55
|
+
|
56
|
+
Foo.instance_eval do
|
57
|
+
define_statemachine do
|
58
|
+
initial_state :a
|
59
|
+
state :a
|
60
|
+
state :b
|
61
|
+
state :c
|
62
|
+
state :d
|
63
|
+
end
|
64
|
+
|
65
|
+
define_statemachine(:alpha) do
|
66
|
+
initial_state :a
|
67
|
+
state :a
|
68
|
+
state :b
|
69
|
+
state :c
|
70
|
+
state :d
|
71
|
+
end
|
72
|
+
|
73
|
+
define_statemachine(:beta) do
|
74
|
+
state_attribute(:status)
|
75
|
+
initial_state :a
|
76
|
+
state :a
|
77
|
+
state :b
|
78
|
+
state :c
|
79
|
+
state :d
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
foo = Foo.find(foo.id)
|
84
|
+
|
85
|
+
assert_equal :b, foo.state
|
86
|
+
assert_equal :c, foo.alpha_state
|
87
|
+
assert_equal :d, foo.status
|
88
|
+
|
89
|
+
# check that initial state works too
|
90
|
+
foo = Foo.create
|
91
|
+
|
92
|
+
foo = Foo.find(foo.id)
|
93
|
+
|
94
|
+
assert_equal :a, foo.state
|
95
|
+
assert_equal :a, foo.alpha_state
|
96
|
+
assert_equal :a, foo.status
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_save_state
|
100
|
+
Foo.instance_eval do
|
101
|
+
define_statemachine do
|
102
|
+
initial_state :a
|
103
|
+
state :a do
|
104
|
+
on :go, :to => :b
|
105
|
+
end
|
106
|
+
state :b
|
107
|
+
state :c
|
108
|
+
state :d
|
109
|
+
end
|
110
|
+
|
111
|
+
define_statemachine(:alpha) do
|
112
|
+
initial_state :a
|
113
|
+
state :a do
|
114
|
+
on :go, :to => :c
|
115
|
+
end
|
116
|
+
state :b
|
117
|
+
state :c
|
118
|
+
state :d
|
119
|
+
end
|
120
|
+
|
121
|
+
define_statemachine(:beta) do
|
122
|
+
state_attribute(:status)
|
123
|
+
initial_state :a
|
124
|
+
state :a do
|
125
|
+
on :go, :to => :d
|
126
|
+
end
|
127
|
+
state :b
|
128
|
+
state :c
|
129
|
+
state :d
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
foo = Foo.create
|
134
|
+
|
135
|
+
assert_equal :a, foo.state
|
136
|
+
assert_equal :a, foo.alpha_state
|
137
|
+
assert_equal :a, foo.status
|
138
|
+
|
139
|
+
foo.go
|
140
|
+
|
141
|
+
assert_equal :b, foo.state
|
142
|
+
assert_equal :c, foo.alpha_state
|
143
|
+
assert_equal :d, foo.status
|
144
|
+
|
145
|
+
foo = Foo.find(foo.id)
|
146
|
+
|
147
|
+
assert_equal :b, foo.state
|
148
|
+
assert_equal :c, foo.alpha_state
|
149
|
+
assert_equal :d, foo.status
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
def test_fire_entry_action_on_initial_state
|
154
|
+
Foo.instance_eval do
|
155
|
+
define_statemachine do
|
156
|
+
initial_state :a
|
157
|
+
state :a do
|
158
|
+
enter do |r|
|
159
|
+
throw :it_worked!
|
160
|
+
end
|
161
|
+
on :go, :to => :b
|
162
|
+
end
|
163
|
+
state :b
|
164
|
+
end
|
165
|
+
|
166
|
+
define_statemachine(:alpha) do
|
167
|
+
initial_state :a
|
168
|
+
state :a do
|
169
|
+
on :go, :to => :c
|
170
|
+
end
|
171
|
+
state :b
|
172
|
+
state :c
|
173
|
+
state :d
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
assert_throws :it_worked! do
|
178
|
+
foo = Foo.create
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def test_transaction_around_fire_event
|
184
|
+
#TODO: check that transaction around fire_event is respected (i.e. changes not persisted when an execption is
|
185
|
+
# raised inside an event firing)
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|