golem_statemachine 0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|