davidlee-state-fu 0.2.0 → 0.3.1
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/README.textile +6 -2
- data/Rakefile +24 -3
- data/lib/no_stdout.rb +28 -5
- data/lib/state-fu.rb +25 -21
- data/lib/state_fu/active_support_lite/misc.rb +57 -0
- data/lib/state_fu/binding.rb +51 -41
- data/lib/state_fu/core_ext.rb +5 -4
- data/lib/state_fu/event.rb +51 -16
- data/lib/state_fu/exceptions.rb +5 -0
- data/lib/state_fu/fu_space.rb +5 -4
- data/lib/state_fu/helper.rb +25 -3
- data/lib/state_fu/hooks.rb +4 -1
- data/lib/state_fu/interface.rb +20 -24
- data/lib/state_fu/lathe.rb +38 -2
- data/lib/state_fu/logger.rb +84 -6
- data/lib/state_fu/machine.rb +3 -0
- data/lib/state_fu/method_factory.rb +3 -3
- data/lib/state_fu/persistence/active_record.rb +3 -1
- data/lib/state_fu/persistence/attribute.rb +4 -4
- data/lib/state_fu/persistence/base.rb +3 -3
- data/lib/state_fu/persistence/relaxdb.rb +23 -0
- data/lib/state_fu/persistence.rb +24 -29
- data/lib/state_fu/plotter.rb +63 -0
- data/lib/state_fu/sprocket.rb +12 -0
- data/lib/state_fu/state.rb +22 -0
- data/lib/state_fu/transition.rb +13 -0
- data/lib/vizier.rb +300 -0
- data/spec/BDD/plotter_spec.rb +115 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/not_requirements_spec.rb +81 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
- data/spec/features/transition_boolean_comparison.rb +90 -0
- data/spec/helper.rb +33 -0
- data/spec/integration/active_record_persistence_spec.rb +0 -1
- data/spec/integration/example_01_document_spec.rb +1 -1
- data/spec/integration/relaxdb_persistence_spec.rb +94 -0
- data/spec/integration/requirement_reflection_spec.rb +2 -2
- data/spec/integration/transition_spec.rb +9 -1
- data/spec/units/binding_spec.rb +46 -17
- data/spec/units/lathe_spec.rb +11 -10
- data/spec/units/method_factory_spec.rb +6 -1
- metadata +37 -23
- data/spec/integration/temp_spec.rb +0 -17
data/lib/state_fu/helper.rb
CHANGED
|
@@ -79,10 +79,12 @@ module StateFu
|
|
|
79
79
|
# is there exactly one possible event to fire, with a single
|
|
80
80
|
# target event?
|
|
81
81
|
def next?
|
|
82
|
+
raise NotImplementedError
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
# if next?, return the state
|
|
85
86
|
def next
|
|
87
|
+
raise NotImplementedError
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
end
|
|
@@ -104,10 +106,12 @@ module StateFu
|
|
|
104
106
|
# is there exactly one possible event to fire, with a single
|
|
105
107
|
# target event?
|
|
106
108
|
def next?
|
|
109
|
+
raise NotImplementedError
|
|
107
110
|
end
|
|
108
111
|
|
|
109
112
|
# if next?, return the event
|
|
110
113
|
def next
|
|
114
|
+
raise NotImplementedError
|
|
111
115
|
end
|
|
112
116
|
|
|
113
117
|
end
|
|
@@ -117,7 +121,9 @@ module StateFu
|
|
|
117
121
|
self.map do |h|
|
|
118
122
|
case h
|
|
119
123
|
when String, Symbol
|
|
120
|
-
|
|
124
|
+
mod_name = h.to_s.split('/').inject(Object) do |mod, part|
|
|
125
|
+
mod = mod.const_get( part.camelize )
|
|
126
|
+
end
|
|
121
127
|
when Module
|
|
122
128
|
h
|
|
123
129
|
else
|
|
@@ -182,11 +188,14 @@ module StateFu
|
|
|
182
188
|
end
|
|
183
189
|
end # OrderedHash
|
|
184
190
|
|
|
191
|
+
# satanic incantations we use for evaluating blocks conditionally,
|
|
192
|
+
# massaging their arguments and managing execution context.
|
|
185
193
|
module ContextualEval
|
|
194
|
+
# :nodoc:
|
|
186
195
|
module InstanceMethods
|
|
187
196
|
|
|
188
197
|
# if we use &block syntax it stuffs the arity up, so we have to
|
|
189
|
-
# pass it as a normal argument
|
|
198
|
+
# pass it as a normal argument. Ruby bug!
|
|
190
199
|
def limit_arguments( block, *args )
|
|
191
200
|
case block.arity
|
|
192
201
|
when -1, 0
|
|
@@ -225,8 +234,21 @@ module StateFu
|
|
|
225
234
|
def evaluate_named_proc_or_method( name, *args )
|
|
226
235
|
if (name.is_a?( Proc ) && proc = name) || proc = machine.named_procs[ name ]
|
|
227
236
|
evaluate( *args, &proc )
|
|
228
|
-
|
|
237
|
+
elsif self.respond_to?( name )
|
|
238
|
+
if method(name).arity == 0
|
|
239
|
+
send(name)
|
|
240
|
+
else
|
|
241
|
+
send(name, *args )
|
|
242
|
+
end
|
|
243
|
+
# evaluate( *args, &method(name) )
|
|
244
|
+
elsif object.respond_to?( name )
|
|
229
245
|
call_on_object_with_optional_args( name, *args )
|
|
246
|
+
else # method is not defined
|
|
247
|
+
if name.to_s =~ /^not_(.*)$/
|
|
248
|
+
!evaluate_named_proc_or_method( $1, *args )
|
|
249
|
+
else
|
|
250
|
+
raise NoMethodError.new("#{name} is not defined on #{object} or #{self} or as a named proc in #{machine}")
|
|
251
|
+
end
|
|
230
252
|
end
|
|
231
253
|
end
|
|
232
254
|
|
data/lib/state_fu/hooks.rb
CHANGED
data/lib/state_fu/interface.rb
CHANGED
|
@@ -6,9 +6,8 @@ module StateFu
|
|
|
6
6
|
|
|
7
7
|
# TODO:
|
|
8
8
|
# take option :alias => false (disable aliases) or :alias
|
|
9
|
-
# => :foo (
|
|
9
|
+
# => :foo (add :foo as class & instance accessor methods)
|
|
10
10
|
|
|
11
|
-
#
|
|
12
11
|
# Given no arguments, return the default machine (:state_fu) for the
|
|
13
12
|
# class, creating it if it did not exist.
|
|
14
13
|
#
|
|
@@ -35,10 +34,9 @@ module StateFu
|
|
|
35
34
|
alias_method :stfu, :machine
|
|
36
35
|
alias_method :state_fu, :machine
|
|
37
36
|
alias_method :workflow, :machine
|
|
37
|
+
alias_method :stateful, :machine
|
|
38
38
|
alias_method :statefully, :machine
|
|
39
39
|
alias_method :state_machine, :machine
|
|
40
|
-
alias_method :stateful, :machine
|
|
41
|
-
alias_method :workflow, :machine
|
|
42
40
|
alias_method :engine, :machine
|
|
43
41
|
|
|
44
42
|
# return a hash of :name => StateFu::Machine for your class.
|
|
@@ -49,22 +47,25 @@ module StateFu
|
|
|
49
47
|
machine( *args, &block)
|
|
50
48
|
end
|
|
51
49
|
end
|
|
52
|
-
alias_method :
|
|
53
|
-
alias_method :
|
|
54
|
-
alias_method :
|
|
50
|
+
alias_method :stfus, :machines
|
|
51
|
+
alias_method :state_fus, :machines
|
|
52
|
+
alias_method :workflows, :machines
|
|
53
|
+
alias_method :engines, :machines
|
|
55
54
|
|
|
56
55
|
# return the list of machines names for this class
|
|
57
56
|
def machine_names()
|
|
58
57
|
StateFu::FuSpace.class_machines[self].keys
|
|
59
58
|
end
|
|
60
|
-
alias_method :
|
|
61
|
-
alias_method :
|
|
62
|
-
alias_method :
|
|
59
|
+
alias_method :stfu_names, :machine_names
|
|
60
|
+
alias_method :state_fu_names, :machine_names
|
|
61
|
+
alias_method :workflow_names, :machine_names
|
|
62
|
+
alias_method :engine_names, :machine_names
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
65
|
+
# These methods grant access to StateFu::Binding objects, which
|
|
66
|
+
# are bundles of context encapsulating a StateFu::Machine, an instance
|
|
67
|
+
# of a class, and its current state in the machine.
|
|
68
|
+
|
|
68
69
|
# Again, plenty of aliases are provided so you can use whatever
|
|
69
70
|
# makes sense to you.
|
|
70
71
|
module InstanceMethods
|
|
@@ -73,16 +74,11 @@ module StateFu
|
|
|
73
74
|
@_state_fu ||= {}
|
|
74
75
|
end
|
|
75
76
|
|
|
76
|
-
# A StateFu::Binding comes into being
|
|
77
|
-
# machine, when you first call yourobject.binding() for that
|
|
78
|
-
# machine.
|
|
79
|
-
#
|
|
80
|
-
# Like the class method .machine(), calling it without any arguments
|
|
81
|
-
# is equivalent to passing :om.
|
|
82
|
-
#
|
|
83
|
-
# Essentially, this is the accessor method through which an instance
|
|
84
|
-
# can see and change its state, interact with events, etc.
|
|
77
|
+
# A StateFu::Binding comes into being when it is first referenced.
|
|
85
78
|
#
|
|
79
|
+
# This is the accessor method through which an object instance (or developer)
|
|
80
|
+
# can access a StateFu::Machine, the object's current state, the
|
|
81
|
+
# methods which trigger event transitions, etc.
|
|
86
82
|
public
|
|
87
83
|
def _binding( name=StateFu::DEFAULT_MACHINE )
|
|
88
84
|
name = name.to_sym
|
|
@@ -97,9 +93,9 @@ module StateFu
|
|
|
97
93
|
alias_method :stateful, :_binding
|
|
98
94
|
alias_method :workflow, :_binding
|
|
99
95
|
alias_method :engine, :_binding
|
|
96
|
+
alias_method :machine, :_binding # not strictly accurate
|
|
100
97
|
alias_method :context, :_binding
|
|
101
98
|
|
|
102
|
-
|
|
103
99
|
# Gain awareness of all bindings (state contexts) this object
|
|
104
100
|
# has contemplated into being.
|
|
105
101
|
# Returns a Hash of { :name => <StateFu::Binding>, ... }
|
|
@@ -114,7 +110,7 @@ module StateFu
|
|
|
114
110
|
alias_method :workflows, :_bindings
|
|
115
111
|
alias_method :engines, :_bindings
|
|
116
112
|
alias_method :bindings, :_bindings
|
|
117
|
-
alias_method :machines, :_bindings # not strictly accurate
|
|
113
|
+
alias_method :machines, :_bindings # not strictly accurate
|
|
118
114
|
alias_method :contexts, :_bindings
|
|
119
115
|
|
|
120
116
|
# Instantiate bindings for all machines defined for this class.
|
data/lib/state_fu/lathe.rb
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
module StateFu
|
|
2
|
+
# A Lathe parses and a Machine definition and returns a freshly turned
|
|
3
|
+
# Machine.
|
|
4
|
+
#
|
|
5
|
+
# It provides the means to define the arrangement of StateFu objects
|
|
6
|
+
# ( eg States and Events) which comprise a workflow, process,
|
|
7
|
+
# lifecycle, circuit, syntax, etc.
|
|
2
8
|
class Lathe
|
|
3
9
|
|
|
4
10
|
# NOTE: Sprocket is the abstract superclass of Event and State
|
|
5
11
|
|
|
6
12
|
attr_reader :machine, :sprocket, :options
|
|
7
13
|
|
|
14
|
+
# you don't need to call this directly.
|
|
8
15
|
def initialize( machine, sprocket = nil, options={}, &block )
|
|
9
16
|
@machine = machine
|
|
10
17
|
@sprocket = sprocket
|
|
@@ -141,7 +148,7 @@ module StateFu
|
|
|
141
148
|
if child? && sprocket.is_a?( StateFu::State ) # in state block
|
|
142
149
|
targets = options.delete(:to)
|
|
143
150
|
evt = define_event( name, options, &block )
|
|
144
|
-
evt.from sprocket unless
|
|
151
|
+
evt.from sprocket unless sprocket.nil?
|
|
145
152
|
evt.to( targets ) unless targets.nil?
|
|
146
153
|
evt
|
|
147
154
|
else # in master lathe
|
|
@@ -225,11 +232,38 @@ module StateFu
|
|
|
225
232
|
sprocket.to( *args, &block )
|
|
226
233
|
end
|
|
227
234
|
|
|
235
|
+
#
|
|
236
|
+
# define chained events and states succinctly
|
|
237
|
+
# usage: chain 'state1 -event1-> state2 -event2-> state3'
|
|
238
|
+
def chain (string)
|
|
239
|
+
rx_word = /([a-zA-Z0-9_]+)/
|
|
240
|
+
rx_state = /^#{rx_word}$/
|
|
241
|
+
rx_event = /^-#{rx_word}->$/
|
|
242
|
+
previous = nil
|
|
243
|
+
string.split.each do |chunk|
|
|
244
|
+
case chunk
|
|
245
|
+
when rx_state
|
|
246
|
+
current = state($1)
|
|
247
|
+
if previous.is_a?( StateFu::Event )
|
|
248
|
+
previous.to( current )
|
|
249
|
+
end
|
|
250
|
+
when rx_event
|
|
251
|
+
current = event($1)
|
|
252
|
+
if previous.is_a?( StateFu::State )
|
|
253
|
+
current.from( previous )
|
|
254
|
+
end
|
|
255
|
+
else
|
|
256
|
+
raise ArgumentError, "'#{chunk}' is not a valid token"
|
|
257
|
+
end
|
|
258
|
+
previous = current
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
228
262
|
#
|
|
229
263
|
# do something with all states / events
|
|
230
264
|
#
|
|
231
265
|
def each_sprocket( type, *args, &block)
|
|
232
|
-
|
|
266
|
+
|
|
233
267
|
options = args.extract_options!.symbolize_keys!
|
|
234
268
|
if args == [:ALL] || args == []
|
|
235
269
|
args = machine.send("#{type}s").except( options.delete(:except) )
|
|
@@ -238,12 +272,14 @@ module StateFu
|
|
|
238
272
|
end
|
|
239
273
|
|
|
240
274
|
def states( *args, &block )
|
|
275
|
+
require_no_sprocket()
|
|
241
276
|
each_sprocket( 'state', *args, &block )
|
|
242
277
|
end
|
|
243
278
|
alias_method :all_states, :states
|
|
244
279
|
alias_method :each_state, :states
|
|
245
280
|
|
|
246
281
|
def events( *args, &block )
|
|
282
|
+
require_sprocket( NilClass, StateFu::State )
|
|
247
283
|
each_sprocket( 'event', *args, &block )
|
|
248
284
|
end
|
|
249
285
|
alias_method :all_events, :events
|
data/lib/state_fu/logger.rb
CHANGED
|
@@ -1,10 +1,88 @@
|
|
|
1
1
|
require 'logger'
|
|
2
|
-
|
|
3
2
|
module StateFu
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
class Logger
|
|
4
|
+
cattr_accessor :prefix # prefix for log messages
|
|
5
|
+
cattr_accessor :suppress # set true to send messages to /dev/null
|
|
6
|
+
|
|
7
|
+
DEBUG = 0
|
|
8
|
+
INFO = 1
|
|
9
|
+
WARN = 2
|
|
10
|
+
ERROR = 3
|
|
11
|
+
FATAL = 4
|
|
12
|
+
UNKNOWN = 5
|
|
13
|
+
|
|
14
|
+
ENV_LOG_LEVEL = 'STATEFU_LOGLEVEL'
|
|
15
|
+
DEFAULT_LEVEL = INFO
|
|
16
|
+
|
|
17
|
+
DEFAULT_PREFIX = nil
|
|
18
|
+
SHARED_LOG_PREFIX = 'StateFu: '
|
|
19
|
+
|
|
20
|
+
@@prefix = DEFAULT_PREFIX
|
|
21
|
+
@@logger = nil
|
|
22
|
+
@@suppress = false
|
|
23
|
+
|
|
24
|
+
def self.level=( new_level )
|
|
25
|
+
instance.level = case new_level
|
|
26
|
+
when String, Symbol
|
|
27
|
+
const_get( new_level )
|
|
28
|
+
when Fixnum
|
|
29
|
+
new_level
|
|
30
|
+
else
|
|
31
|
+
state_fu_log_level()
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.state_fu_log_level
|
|
36
|
+
if ENV[ ENV_LOG_LEVEL ]
|
|
37
|
+
const_get( ENV[ ENV_LOG_LEVEL ] )
|
|
38
|
+
else
|
|
39
|
+
DEFAULT_LEVEL
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.new( log = $stdout, level = () )
|
|
44
|
+
self.instance = get_logger( log )
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.instance=( logger )
|
|
48
|
+
@@logger ||= get_logger
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.instance
|
|
52
|
+
@@logger ||= get_logger
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.get_logger( log = $stdout )
|
|
56
|
+
if Object.const_defined?( "RAILS_DEFAULT_LOGGER" )
|
|
57
|
+
logger = RAILS_DEFAULT_LOGGER
|
|
58
|
+
prefix = SHARED_LOG_PREFIX
|
|
59
|
+
else
|
|
60
|
+
if Object.const_defined?( 'ActiveSupport' ) && ActiveSupport.const_defined?('BufferedLogger')
|
|
61
|
+
logger = ActiveSupport::BufferedLogger.new( log )
|
|
62
|
+
else
|
|
63
|
+
logger = ::Logger.new( log )
|
|
64
|
+
logger.level = state_fu_log_level()
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
logger
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.suppress!
|
|
71
|
+
@@suppress = true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# method_missing is usually a last resort
|
|
75
|
+
# but i don't see it causing any headaches here.
|
|
76
|
+
def self.method_missing( method_id, *args )
|
|
77
|
+
return if @@suppress
|
|
78
|
+
if [:debug, :info, :warn, :error, :fatal].include?( method_id ) &&
|
|
79
|
+
args[0].is_a?(String) && @@prefix
|
|
80
|
+
args[0] = @@prefix + args[0]
|
|
81
|
+
end
|
|
82
|
+
instance.send( method_id, *args )
|
|
83
|
+
end
|
|
84
|
+
|
|
9
85
|
end
|
|
10
86
|
end
|
|
87
|
+
|
|
88
|
+
# StateFu::Logger.info( StateFu::Logger.instance.inspect )
|
data/lib/state_fu/machine.rb
CHANGED
|
@@ -78,14 +78,14 @@ module StateFu
|
|
|
78
78
|
# obj.event_name?( target )
|
|
79
79
|
# true if the event is fireable? (ie, requirements met)
|
|
80
80
|
method_name = "#{event.name}?"
|
|
81
|
-
define_method_on_metaclass( obj, method_name ) do |target|
|
|
82
|
-
_binding.fireable?( [event, target] )
|
|
81
|
+
define_method_on_metaclass( obj, method_name ) do |target, *args|
|
|
82
|
+
_binding.fireable?( [event, target], *args )
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
# obj.event_name!( target, *args )
|
|
86
86
|
# creates, fires and returns a transition
|
|
87
87
|
method_name = "#{event.name}!"
|
|
88
|
-
define_method_on_metaclass( obj, method_name ) do |target
|
|
88
|
+
define_method_on_metaclass( obj, method_name ) do |target, *args|
|
|
89
89
|
_binding.fire!( [event, target], *args )
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -4,6 +4,7 @@ module StateFu
|
|
|
4
4
|
|
|
5
5
|
def self.prepare_field( klass, field_name )
|
|
6
6
|
_field_name = field_name
|
|
7
|
+
Logger.debug("Preparing ActiveRecord field #{klass}.#{field_name}")
|
|
7
8
|
klass.send :before_save, :state_fu!
|
|
8
9
|
# validates_presence_of _field_name
|
|
9
10
|
end
|
|
@@ -14,11 +15,12 @@ module StateFu
|
|
|
14
15
|
# Attribute version, so just do the simplest thing we can.
|
|
15
16
|
|
|
16
17
|
def read_attribute
|
|
18
|
+
Logger.debug "Read attribute #{field_name}, got #{object.send(:read_attribute,field_name)} for #{object.inspect}"
|
|
17
19
|
object.send( :read_attribute, field_name )
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def write_attribute( string_value )
|
|
21
|
-
|
|
23
|
+
Logger.debug "Write attribute #{field_name} to #{string_value} for #{object.inspect}"
|
|
22
24
|
object.send( :write_attribute, field_name, string_value )
|
|
23
25
|
end
|
|
24
26
|
|
|
@@ -5,7 +5,7 @@ module StateFu
|
|
|
5
5
|
def self.prepare_field( klass, field_name )
|
|
6
6
|
# ensure getter exists
|
|
7
7
|
unless klass.instance_methods.map(&:to_sym).include?( field_name.to_sym )
|
|
8
|
-
Logger.
|
|
8
|
+
Logger.debug "Adding attr_reader :#{field_name} for #{klass}"
|
|
9
9
|
_field_name = field_name
|
|
10
10
|
klass.class_eval do
|
|
11
11
|
private
|
|
@@ -15,7 +15,7 @@ module StateFu
|
|
|
15
15
|
|
|
16
16
|
# ensure setter exists
|
|
17
17
|
unless klass.instance_methods.map(&:to_sym).include?( :"#{field_name}=" )
|
|
18
|
-
Logger.
|
|
18
|
+
Logger.debug "Adding attr_writer :#{field_name}= for #{klass}"
|
|
19
19
|
_field_name = field_name
|
|
20
20
|
klass.class_eval do
|
|
21
21
|
private
|
|
@@ -31,13 +31,13 @@ module StateFu
|
|
|
31
31
|
|
|
32
32
|
def read_attribute
|
|
33
33
|
string = object.send( field_name )
|
|
34
|
-
Logger.
|
|
34
|
+
Logger.debug "Read attribute #{field_name}, got #{string.inspect} for #{object.inspect}"
|
|
35
35
|
string
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def write_attribute( string_value )
|
|
39
39
|
writer_method = "#{field_name}="
|
|
40
|
-
Logger.
|
|
40
|
+
Logger.debug "Writing attribute #{field_name} -> #{string_value.inspect} for #{object.inspect}"
|
|
41
41
|
object.send( writer_method, string_value )
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -34,11 +34,11 @@ module StateFu
|
|
|
34
34
|
@current_state = find_current_state()
|
|
35
35
|
|
|
36
36
|
if current_state.nil?
|
|
37
|
-
Logger.
|
|
38
|
-
Logger.
|
|
37
|
+
Logger.warn("undefined state for binding #{binding} on #{object} with field_name #{field_name.inspect}")
|
|
38
|
+
Logger.warn("Machine for #{object} has no states: #{machine}") if machine.states.empty?
|
|
39
39
|
else
|
|
40
40
|
persist!
|
|
41
|
-
|
|
41
|
+
Logger.debug("#{object} resumes #{binding.method_name} at #{current_state.name}")
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
module Persistence
|
|
3
|
+
class RelaxDB < StateFu::Persistence::Base
|
|
4
|
+
|
|
5
|
+
def self.prepare_field( klass, field_name )
|
|
6
|
+
_field_name = field_name
|
|
7
|
+
#puts "relaxdb.before_save?"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def read_attribute
|
|
13
|
+
object.send( field_name )
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write_attribute( string_value )
|
|
17
|
+
object.send( "#{field_name}=", string_value )
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
data/lib/state_fu/persistence.rb
CHANGED
|
@@ -2,27 +2,18 @@ module StateFu
|
|
|
2
2
|
module Persistence
|
|
3
3
|
DEFAULT_FIELD_NAME_SUFFIX = '_field'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
else
|
|
14
|
-
state_fu!
|
|
15
|
-
if respond_to?(method_name)
|
|
16
|
-
send( *args, &block )
|
|
17
|
-
else
|
|
18
|
-
method_missing_before_state_fu( *args, &block )
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end # method_missing
|
|
22
|
-
end # class_eval
|
|
23
|
-
end # prepare_class
|
|
24
|
-
|
|
5
|
+
# checks to see if the field_name for persistence is a
|
|
6
|
+
# RelaxDB attribute.
|
|
7
|
+
# Safe to use if RelaxDB is not included.
|
|
8
|
+
def self.relaxdb_document_property?( klass, field_name )
|
|
9
|
+
Object.const_defined?('RelaxDB') &&
|
|
10
|
+
klass.ancestors.include?( ::RelaxDB::Document ) &&
|
|
11
|
+
klass.properties.map(&:to_s).include?( field_name.to_s )
|
|
12
|
+
end
|
|
25
13
|
|
|
14
|
+
# checks to see if the field_name for persistence is an
|
|
15
|
+
# ActiveRecord column.
|
|
16
|
+
# Safe to use if ActiveRecord is not included.
|
|
26
17
|
def self.active_record_column?( klass, field_name )
|
|
27
18
|
Object.const_defined?("ActiveRecord") &&
|
|
28
19
|
::ActiveRecord.const_defined?("Base") &&
|
|
@@ -30,20 +21,24 @@ module StateFu
|
|
|
30
21
|
klass.columns.map(&:name).include?( field_name.to_s )
|
|
31
22
|
end
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
# returns the appropriate persister class for the given class & field name.
|
|
25
|
+
def self.class_for( klass, field_name )
|
|
26
|
+
if active_record_column?( klass, field_name )
|
|
27
|
+
self::ActiveRecord
|
|
28
|
+
elsif relaxdb_document_property?( klass, field_name )
|
|
29
|
+
self::RelaxDB
|
|
36
30
|
else
|
|
37
|
-
self::Attribute
|
|
31
|
+
self::Attribute
|
|
38
32
|
end
|
|
39
33
|
end
|
|
40
34
|
|
|
35
|
+
# returns a persister appropriate to the given binding and field_name
|
|
36
|
+
def self.for( binding, field_name )
|
|
37
|
+
class_for( binding.object.class, field_name ).new( binding, field_name )
|
|
38
|
+
end
|
|
39
|
+
|
|
41
40
|
def self.prepare_field( klass, field_name )
|
|
42
|
-
|
|
43
|
-
self::ActiveRecord.prepare_field( klass, field_name )
|
|
44
|
-
else
|
|
45
|
-
self::Attribute.prepare_field( klass, field_name )
|
|
46
|
-
end
|
|
41
|
+
class_for( klass, field_name ).prepare_field( klass, field_name )
|
|
47
42
|
end
|
|
48
43
|
|
|
49
44
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__),'../vizier')
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
|
|
4
|
+
module StateFu
|
|
5
|
+
class Plotter
|
|
6
|
+
attr_reader :machine, :dot, :graph, :states, :events
|
|
7
|
+
|
|
8
|
+
OUTPUT_HELPER = Module.new do
|
|
9
|
+
|
|
10
|
+
def save!
|
|
11
|
+
Tempfile.new(['state_fu_graph','.dot']) do |fh|
|
|
12
|
+
fh.write( self )
|
|
13
|
+
end.path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def save_as( filename )
|
|
17
|
+
File.open(filename, 'w') { |fh| fh.write( self ) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def save_png(filename)
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
# dot graph.dot -Tpng -O
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def output
|
|
28
|
+
generate
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize( machine, options={} )
|
|
32
|
+
raise RuntimeError, machine.class.to_s unless machine.is_a?(StateFu::Machine)
|
|
33
|
+
@machine = machine
|
|
34
|
+
@options = options.symbolize_keys!
|
|
35
|
+
@states = {}
|
|
36
|
+
@events = {}
|
|
37
|
+
# generate
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def generate
|
|
41
|
+
@dot ||= generate_dot!.extend( OUTPUT_HELPER )
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def generate_dot!
|
|
45
|
+
@graph = Vizier::Graph.new(:state_machine) do |g|
|
|
46
|
+
g.node :shape => 'doublecircle'
|
|
47
|
+
machine.state_names.map.each do |s|
|
|
48
|
+
@states[s] = g.add_node(s.to_s)
|
|
49
|
+
end
|
|
50
|
+
machine.events.map.each do |e|
|
|
51
|
+
e.origins.map(&:name).each do |from|
|
|
52
|
+
e.targets.map(&:name).each do |to|
|
|
53
|
+
g.connect( @states[from], @states[to], :label => e.name.to_s )
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
# @events[s] = g.add_node(s.to_s)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
@graph.generate!
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/state_fu/sprocket.rb
CHANGED
|
@@ -26,6 +26,18 @@ module StateFu
|
|
|
26
26
|
raise NotImeplementedError # abstract
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
def to_s
|
|
30
|
+
"#<#{self.class}::#{self.object_id} @name=#{name.inspect}>"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def []v
|
|
34
|
+
options[v]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def []=v,k
|
|
38
|
+
options[v]=k
|
|
39
|
+
end
|
|
40
|
+
|
|
29
41
|
end
|
|
30
42
|
end
|
|
31
43
|
|
data/lib/state_fu/state.rb
CHANGED
|
@@ -41,5 +41,27 @@ module StateFu
|
|
|
41
41
|
def === other
|
|
42
42
|
self.to_sym === other.to_sym
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
# display nice and short
|
|
46
|
+
def inspect
|
|
47
|
+
s = self.to_s
|
|
48
|
+
s = s[0,s.length-1]
|
|
49
|
+
display_hooks = hooks.dup
|
|
50
|
+
display_hooks.each do |k,v|
|
|
51
|
+
display_hooks.delete(k) if v.empty?
|
|
52
|
+
end
|
|
53
|
+
unless display_hooks.empty?
|
|
54
|
+
s << " hooks=#{display_hooks.inspect}"
|
|
55
|
+
end
|
|
56
|
+
unless entry_requirements.empty?
|
|
57
|
+
s << " entry_requirements=#{entry_requirements.inspect}"
|
|
58
|
+
end
|
|
59
|
+
unless exit_requirements.empty?
|
|
60
|
+
s << " exit_requirements=#{exit_requirements.inspect}"
|
|
61
|
+
end
|
|
62
|
+
s << ">"
|
|
63
|
+
s
|
|
64
|
+
end
|
|
65
|
+
|
|
44
66
|
end
|
|
45
67
|
end
|