davidlee-state-fu 0.3.1 → 0.10.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.
- data/README.textile +124 -34
- data/Rakefile +36 -30
- data/lib/no_stdout.rb +1 -1
- data/lib/state-fu.rb +9 -8
- data/lib/state_fu/active_support_lite/array/access.rb +12 -5
- data/lib/state_fu/active_support_lite/array/conversions.rb +10 -4
- data/lib/state_fu/active_support_lite/array/extract_options.rb +5 -4
- data/lib/state_fu/active_support_lite/array/grouping.rb +7 -4
- data/lib/state_fu/active_support_lite/array/random_access.rb +4 -3
- data/lib/state_fu/active_support_lite/array/wrapper.rb +4 -3
- data/lib/state_fu/active_support_lite/array.rb +3 -1
- data/lib/state_fu/active_support_lite/blank.rb +18 -9
- data/lib/state_fu/active_support_lite/cattr_reader.rb +4 -1
- data/lib/state_fu/active_support_lite/keys.rb +8 -3
- data/lib/state_fu/active_support_lite/misc.rb +6 -4
- data/lib/state_fu/active_support_lite/module/delegation.rb +130 -0
- data/lib/state_fu/active_support_lite/module.rb +1 -0
- data/lib/state_fu/active_support_lite/object.rb +5 -2
- data/lib/state_fu/active_support_lite/string.rb +6 -1
- data/lib/state_fu/active_support_lite/symbol.rb +2 -1
- data/lib/state_fu/applicable.rb +41 -0
- data/lib/state_fu/{helper.rb → arrays.rb} +45 -121
- data/lib/state_fu/binding.rb +136 -159
- data/lib/state_fu/core_ext.rb +78 -10
- data/lib/state_fu/event.rb +112 -48
- data/lib/state_fu/exceptions.rb +80 -34
- data/lib/state_fu/executioner.rb +149 -0
- data/lib/state_fu/has_options.rb +16 -0
- data/lib/state_fu/hooks.rb +21 -16
- data/lib/state_fu/interface.rb +80 -83
- data/lib/state_fu/lathe.rb +361 -148
- data/lib/state_fu/logger.rb +122 -45
- data/lib/state_fu/machine.rb +60 -32
- data/lib/state_fu/method_factory.rb +180 -72
- data/lib/state_fu/methodical.rb +17 -0
- data/lib/state_fu/persistence/active_record.rb +6 -1
- data/lib/state_fu/persistence/attribute.rb +1 -0
- data/lib/state_fu/persistence/base.rb +8 -6
- data/lib/state_fu/persistence.rb +94 -23
- data/lib/state_fu/sprocket.rb +26 -11
- data/lib/state_fu/state.rb +8 -27
- data/lib/state_fu/transition.rb +207 -98
- data/lib/state_fu/transition_query.rb +214 -0
- data/lib/state_fu.rb +1 -0
- data/lib/tasks/spec_last.rake +46 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/vizier.rb +61 -61
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +2 -2
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +83 -46
- data/spec/features/plotter_spec.rb +97 -0
- data/spec/features/shared_log_spec.rb +7 -0
- data/spec/features/singleton_machine_spec.rb +39 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +1 -1
- data/spec/features/{transition_boolean_comparison.rb → transition_boolean_comparison_spec.rb} +29 -18
- data/spec/helper.rb +6 -117
- data/spec/integration/active_record_persistence_spec.rb +18 -4
- data/spec/integration/binding_extension_spec.rb +1 -1
- data/spec/integration/class_accessor_spec.rb +49 -59
- data/spec/integration/event_definition_spec.rb +20 -20
- data/spec/integration/example_01_document_spec.rb +13 -8
- data/spec/integration/example_02_string_spec.rb +3 -2
- data/spec/integration/instance_accessor_spec.rb +16 -19
- data/spec/integration/lathe_extension_spec.rb +2 -2
- data/spec/integration/machine_duplication_spec.rb +59 -37
- data/spec/integration/relaxdb_persistence_spec.rb +6 -3
- data/spec/integration/requirement_reflection_spec.rb +66 -57
- data/spec/integration/state_definition_spec.rb +72 -66
- data/spec/integration/transition_spec.rb +169 -173
- data/spec/spec.opts +5 -3
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +870 -0
- data/spec/units/binding_spec.rb +33 -22
- data/spec/units/event_spec.rb +3 -22
- data/spec/units/exceptions_spec.rb +7 -0
- data/spec/units/lathe_spec.rb +7 -7
- data/spec/units/machine_spec.rb +67 -75
- data/spec/units/method_factory_spec.rb +55 -48
- data/spec/units/sprocket_spec.rb +5 -7
- data/spec/units/state_spec.rb +33 -24
- metadata +31 -19
- data/lib/state_fu/active_support_lite/inheritable_attributes.rb +0 -1
- data/lib/state_fu/fu_space.rb +0 -51
- data/lib/state_fu/mock_transition.rb +0 -38
- data/spec/BDD/plotter_spec.rb +0 -115
- data/spec/integration/dynamic_requirement_spec.rb +0 -160
- data/spec/integration/ex_machine_for_accounts_spec.rb +0 -79
- data/spec/integration/sanity_spec.rb +0 -31
- data/spec/units/fu_space_spec.rb +0 -95
data/lib/state_fu/event.rb
CHANGED
|
@@ -1,14 +1,54 @@
|
|
|
1
1
|
module StateFu
|
|
2
2
|
class Event < StateFu::Sprocket
|
|
3
3
|
|
|
4
|
-
attr_reader :origins, :targets, :requirements
|
|
4
|
+
attr_reader :origins, :targets, :requirements, :sequence
|
|
5
5
|
|
|
6
6
|
# called by Lathe when a new event is constructed
|
|
7
7
|
def initialize(machine, name, options={})
|
|
8
|
-
@requirements = [].extend ArrayWithSymbolAccessor
|
|
8
|
+
@requirements = [].extend ArrayWithSymbolAccessor
|
|
9
|
+
@sequence = {}
|
|
9
10
|
super( machine, name, options )
|
|
10
11
|
end
|
|
11
12
|
|
|
13
|
+
#
|
|
14
|
+
# build a hash of target => [origins]
|
|
15
|
+
#
|
|
16
|
+
def add_to_sequence origin_states, target_state
|
|
17
|
+
origin_states = [origin_states].flatten
|
|
18
|
+
existing = origin_states.select {|s| target_for_origin(s) }
|
|
19
|
+
raise ArgumentError.new unless existing.empty? && !targets
|
|
20
|
+
@sequence[target_state] ||= []
|
|
21
|
+
[origin_states].flatten.each do |o|
|
|
22
|
+
@sequence[target_state] << o
|
|
23
|
+
end
|
|
24
|
+
@sequence
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def target_for_origin origin_state
|
|
28
|
+
raise ArgumentError.new if origin_state.nil?
|
|
29
|
+
name = sequence.detect do |k,v|
|
|
30
|
+
v.include?(origin_state.to_sym)
|
|
31
|
+
end[0] rescue nil
|
|
32
|
+
machine.states[name] if name
|
|
33
|
+
# if t
|
|
34
|
+
# puts t.inspect + " <============================" if t
|
|
35
|
+
# puts "======================"
|
|
36
|
+
# puts origin_state.class
|
|
37
|
+
# puts origin_state.name rescue origin_state.inspect
|
|
38
|
+
# end
|
|
39
|
+
# machine.states[t.first] if t
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def can_transition_from?(origin_state)
|
|
43
|
+
( origins && origins.include?(origin_state.to_sym) && !targets.blank?) ||
|
|
44
|
+
target_for_origin(origin_state)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def sequence?
|
|
48
|
+
!sequence.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
12
52
|
# the names of all possible origin states
|
|
13
53
|
def origin_names
|
|
14
54
|
origins ? origins.map(&:to_sym) : nil
|
|
@@ -26,26 +66,11 @@ module StateFu
|
|
|
26
66
|
|
|
27
67
|
# tests if a state or state name is in the list of origins
|
|
28
68
|
def from?( state )
|
|
29
|
-
origin_names.include?( state.to_sym )
|
|
69
|
+
origin_names.include?( state.to_sym ) || target_for_origin(state)
|
|
30
70
|
end
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# ensures that calling #from multiple times adds to, rather than
|
|
35
|
-
# clobbering, the list of origins / targets.
|
|
36
|
-
def update_state_collection( ivar_name, *args)
|
|
37
|
-
new_states = if [args].flatten == [:ALL]
|
|
38
|
-
machine.states
|
|
39
|
-
else
|
|
40
|
-
machine.find_or_create_states_by_name( *args.flatten )
|
|
41
|
-
end
|
|
42
|
-
unless new_states.is_a?( Array )
|
|
43
|
-
new_states = [new_states]
|
|
44
|
-
end
|
|
45
|
-
existing = instance_variable_get( ivar_name )
|
|
46
|
-
# return existing if new_states.empty?
|
|
47
|
-
new_value = ((existing || [] ) + new_states).flatten.compact.uniq.extend( StateArray )
|
|
48
|
-
instance_variable_set( ivar_name, new_value )
|
|
71
|
+
|
|
72
|
+
def cycle?
|
|
73
|
+
origin && origin == target
|
|
49
74
|
end
|
|
50
75
|
|
|
51
76
|
# *adds to* the origin states given a list of symbols / States
|
|
@@ -58,17 +83,6 @@ module StateFu
|
|
|
58
83
|
update_state_collection( '@targets', *args )
|
|
59
84
|
end
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
# used internally
|
|
63
|
-
#
|
|
64
|
-
# <tt>complete?(:origins) # do we have origins?<tt>
|
|
65
|
-
# <tt>complete? # do we have origins and targets?<tt>
|
|
66
|
-
def complete?( field = nil )
|
|
67
|
-
( field && [field] || [:origins, :targets] ).
|
|
68
|
-
map{ |s| send(s) }.
|
|
69
|
-
all?{ |f| !(f.nil? || f.empty?) }
|
|
70
|
-
end
|
|
71
|
-
|
|
72
86
|
# if there is a single state in #origins, returns it
|
|
73
87
|
def origin
|
|
74
88
|
origins && origins.length == 1 && origins[0] || nil
|
|
@@ -83,7 +97,22 @@ module StateFu
|
|
|
83
97
|
# origins. It's simple because it can be triggered without
|
|
84
98
|
# supplying a target name - ie, <tt>go!<tt> vs <tt>go!(:home)<tt>
|
|
85
99
|
def simple?
|
|
86
|
-
!! ( origins && target )
|
|
100
|
+
!! ( origins && target || sequence? )
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def fireable?( transition )
|
|
104
|
+
transition.valid?(true)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
#
|
|
109
|
+
# Lathe methods
|
|
110
|
+
#
|
|
111
|
+
|
|
112
|
+
# adds an event requirement.
|
|
113
|
+
# DOCME // TODO - can this be removed?
|
|
114
|
+
def requires( *args, &block )
|
|
115
|
+
lathe.requires( *args, &block )
|
|
87
116
|
end
|
|
88
117
|
|
|
89
118
|
# generally called from a Lathe. Sets the origin(s) and optionally
|
|
@@ -93,17 +122,17 @@ module StateFu
|
|
|
93
122
|
def from *args
|
|
94
123
|
options = args.extract_options!.symbolize_keys!
|
|
95
124
|
args.flatten!
|
|
96
|
-
to = options.delete(:to)
|
|
125
|
+
to = options.delete(:to) || options.delete(:transitions_to)
|
|
97
126
|
if args.empty? && !to
|
|
98
127
|
if options.length == 1
|
|
99
|
-
self.origins= options.keys[0]
|
|
100
|
-
self.targets= options.values[0]
|
|
128
|
+
self.origins = options.keys[0]
|
|
129
|
+
self.targets = options.values[0]
|
|
101
130
|
else
|
|
102
131
|
raise options.inspect
|
|
103
132
|
end
|
|
104
133
|
else
|
|
105
|
-
self.origins= *args
|
|
106
|
-
self.targets= to unless to.nil?
|
|
134
|
+
self.origins = *args
|
|
135
|
+
self.targets = to unless to.nil?
|
|
107
136
|
end
|
|
108
137
|
end
|
|
109
138
|
|
|
@@ -115,18 +144,53 @@ module StateFu
|
|
|
115
144
|
self.targets= *args
|
|
116
145
|
end
|
|
117
146
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
147
|
+
alias_method :transitions_to, :to
|
|
148
|
+
alias_method :transitions_from, :from
|
|
149
|
+
|
|
150
|
+
#
|
|
151
|
+
# misc
|
|
152
|
+
#
|
|
153
|
+
|
|
154
|
+
# display nice and short
|
|
155
|
+
def inspect
|
|
156
|
+
s = self.to_s
|
|
157
|
+
s = s[0,s.length-1]
|
|
158
|
+
display_hooks = hooks.dup
|
|
159
|
+
display_hooks.each do |k,v|
|
|
160
|
+
display_hooks.delete(k) if v.empty?
|
|
161
|
+
end
|
|
162
|
+
unless display_hooks.empty?
|
|
163
|
+
s << " hooks=#{display_hooks.inspect}"
|
|
164
|
+
end
|
|
165
|
+
unless requirements.empty?
|
|
166
|
+
s << " requirements=#{requirements.inspect}"
|
|
167
|
+
end
|
|
168
|
+
s << " targets=#{targets.map(&:to_sym).inspect}" if targets
|
|
169
|
+
s << " origins=#{origins.map(&:to_sym).inspect}" if origins
|
|
170
|
+
s << ">"
|
|
171
|
+
s
|
|
124
172
|
end
|
|
125
173
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
# internal method which accumulates states into an instance
|
|
177
|
+
# variable with successive invocations.
|
|
178
|
+
# ensures that calling #from multiple times adds to, rather than
|
|
179
|
+
# clobbering, the list of origins / targets.
|
|
180
|
+
def update_state_collection( ivar_name, *args)
|
|
181
|
+
raise ArgumentError if sequence?
|
|
182
|
+
new_states = if [args].flatten == [:ALL]
|
|
183
|
+
machine.states
|
|
184
|
+
else
|
|
185
|
+
machine.find_or_create_states_by_name( *args.flatten )
|
|
186
|
+
end
|
|
187
|
+
unless new_states.is_a?( Array )
|
|
188
|
+
new_states = [new_states]
|
|
189
|
+
end
|
|
190
|
+
existing = instance_variable_get( ivar_name )
|
|
191
|
+
# return existing if new_states.empty?
|
|
192
|
+
new_value = ((existing || [] ) + new_states).flatten.compact.uniq.extend( StateArray )
|
|
193
|
+
instance_variable_set( ivar_name, new_value )
|
|
130
194
|
end
|
|
131
195
|
|
|
132
196
|
end
|
data/lib/state_fu/exceptions.rb
CHANGED
|
@@ -1,59 +1,105 @@
|
|
|
1
1
|
module StateFu
|
|
2
2
|
|
|
3
|
-
class
|
|
3
|
+
class MagicMethodError < NoMethodError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Error < ::StandardError
|
|
4
7
|
attr_reader :binding, :options
|
|
8
|
+
|
|
9
|
+
def initialize binding, message=nil, options={}
|
|
10
|
+
@binding = binding
|
|
11
|
+
@options = options
|
|
12
|
+
super message
|
|
13
|
+
end
|
|
14
|
+
|
|
5
15
|
end
|
|
6
16
|
|
|
7
|
-
class
|
|
8
|
-
attr_reader :
|
|
9
|
-
|
|
17
|
+
class TransitionNotFound < Error
|
|
18
|
+
attr_reader :valid_transitions
|
|
19
|
+
attr_reader :valid_destinations
|
|
20
|
+
DEFAULT_MESSAGE = "Transition could not be determined"
|
|
21
|
+
|
|
22
|
+
def initialize(binding, valid_transitions, message=DEFAULT_MESSAGE, options={})
|
|
23
|
+
@valid_transitions = valid_transitions
|
|
24
|
+
@valid_destinations = valid_transitions.map(&:destination)
|
|
25
|
+
super(binding, message, options)
|
|
26
|
+
end
|
|
10
27
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
transition.unmet_requirements
|
|
28
|
+
def inspect
|
|
29
|
+
"<#{self.class.to_s} #{message} available=[#{valid_destinations.inspect}]>"
|
|
14
30
|
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class TransitionError < Error
|
|
35
|
+
# TODO default message
|
|
36
|
+
attr_reader :transition
|
|
15
37
|
|
|
16
|
-
def initialize
|
|
17
|
-
|
|
18
|
-
@
|
|
19
|
-
super
|
|
38
|
+
def initialize transition, message=nil, options={}
|
|
39
|
+
raise caller.inspect unless transition.is_a?(Transition)
|
|
40
|
+
@transition = transition
|
|
41
|
+
super transition.binding, message, options
|
|
20
42
|
end
|
|
21
43
|
|
|
44
|
+
delegate :origin, :to => :transition
|
|
45
|
+
delegate :target, :to => :transition
|
|
46
|
+
delegate :event, :to => :transition
|
|
47
|
+
delegate :args, :to => :transition
|
|
48
|
+
|
|
49
|
+
# TODO capture these on initialization
|
|
50
|
+
delegate :unmet_requirements, :to => :transition
|
|
51
|
+
delegate :unmet_requirement_messages, :to => :transition
|
|
52
|
+
delegate :requirement_errors, :to => :transition
|
|
53
|
+
|
|
22
54
|
def inspect
|
|
23
|
-
|
|
55
|
+
origin_name = origin && origin.name
|
|
56
|
+
target_name = target && target.name
|
|
57
|
+
event_name = event && event.name
|
|
58
|
+
"<#{self.class.to_s} #{message} #{origin_name.inspect}=[#{event_name.inspect}]=>#{target_name.inspect}>"
|
|
24
59
|
end
|
|
25
60
|
end
|
|
26
61
|
|
|
27
|
-
class
|
|
28
|
-
|
|
62
|
+
class UnknownTarget < TransitionError
|
|
63
|
+
end
|
|
29
64
|
|
|
30
|
-
|
|
65
|
+
class TransitionAlreadyFired < TransitionError
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class RequirementError < TransitionError
|
|
69
|
+
include Enumerable
|
|
31
70
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
71
|
+
delegate :each, :to => :to_h
|
|
72
|
+
delegate :length, :to => :to_h
|
|
73
|
+
delegate :empty?, :to => :to_h
|
|
74
|
+
|
|
75
|
+
def to_a
|
|
76
|
+
unmet_requirement_messages
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_h
|
|
80
|
+
requirement_errors
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_s
|
|
84
|
+
inspect
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def inspect
|
|
88
|
+
"<#{self.class.to_s}::#{__id__} :#{transition.origin.to_sym}-[#{transition.event.to_sym}]->:#{transition.target.to_sym} unmet_requirements=#{to_a.inspect}>"
|
|
36
89
|
end
|
|
37
90
|
end
|
|
38
91
|
|
|
39
|
-
class
|
|
40
|
-
|
|
92
|
+
class TransitionHalted < TransitionError
|
|
93
|
+
end
|
|
41
94
|
|
|
42
|
-
|
|
95
|
+
class InvalidTransition < TransitionError
|
|
96
|
+
attr_reader :valid_transitions
|
|
43
97
|
|
|
44
|
-
def initialize
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
target,
|
|
48
|
-
message=DEFAULT_MESSAGE,
|
|
49
|
-
options={})
|
|
50
|
-
@binding = binding
|
|
51
|
-
@event = event
|
|
52
|
-
@origin = origin
|
|
53
|
-
@target = target
|
|
54
|
-
@options = options
|
|
55
|
-
super( message )
|
|
98
|
+
def initialize transition, message=nil, valid_transitions=nil, options={}
|
|
99
|
+
@valid_transitions = valid_transitions
|
|
100
|
+
super transition, message, options
|
|
56
101
|
end
|
|
102
|
+
|
|
57
103
|
end
|
|
58
104
|
|
|
59
105
|
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
#
|
|
3
|
+
# delegator class for evaluation methods / procs in the context of
|
|
4
|
+
# your object.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# There's a bug in ruby 1.8.x where lambda {}.arity == -1 instead of 0
|
|
8
|
+
# To get around this, turn it into a proc if conditions are dangerous.
|
|
9
|
+
def self.get_effective_arity
|
|
10
|
+
if RUBY_VERSION[0,3] == "1.8" && proc.arity == -1
|
|
11
|
+
proc.to_proc.arity
|
|
12
|
+
else
|
|
13
|
+
proc.arity
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Executioner
|
|
18
|
+
|
|
19
|
+
# give us a blank slate
|
|
20
|
+
# instance_methods.each { |m| undef_method m unless m =~ /(^__|^self|^nil\?$|^send$|proxy_|^object_id|^respond_to\?|^instance_exec|^instance_eval|^method$)/ }
|
|
21
|
+
|
|
22
|
+
def initialize transition, &block
|
|
23
|
+
@transition = transition
|
|
24
|
+
@__target__ = transition.object
|
|
25
|
+
@__self___ = self
|
|
26
|
+
yield self if block_given?
|
|
27
|
+
# forces method_missing to snap back to its pre-state-fu condition:
|
|
28
|
+
# @__target__.initialize_state_fu!
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# delegate :self, :to => :__target__
|
|
33
|
+
|
|
34
|
+
delegate :origin, :to => :transition, :prefix => true # transition_origin
|
|
35
|
+
delegate :target, :to => :transition, :prefix => true # transition_target
|
|
36
|
+
delegate :event, :to => :transition, :prefix => true # transition_event
|
|
37
|
+
|
|
38
|
+
delegate :halt!, :to => :transition
|
|
39
|
+
delegate :args, :to => :transition
|
|
40
|
+
delegate :options, :to => :transition
|
|
41
|
+
|
|
42
|
+
def binding
|
|
43
|
+
transition.binding
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :transition, :__target__, :__self__
|
|
47
|
+
|
|
48
|
+
alias_method :t, :transition
|
|
49
|
+
alias_method :current_transition, :transition
|
|
50
|
+
alias_method :context, :transition
|
|
51
|
+
alias_method :ctx, :transition
|
|
52
|
+
|
|
53
|
+
alias_method :arguments, :args
|
|
54
|
+
alias_method :transition_arguments, :args
|
|
55
|
+
|
|
56
|
+
# delegate :machine, :to => :binding
|
|
57
|
+
#delegate :states, :to => :machine
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def machine
|
|
61
|
+
binding.machine
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def states
|
|
65
|
+
# puts binding
|
|
66
|
+
# puts binding.instance_eval() { @machine }
|
|
67
|
+
# puts binding.machine
|
|
68
|
+
# puts binding.machine.states.names
|
|
69
|
+
# puts machine
|
|
70
|
+
# puts machine.states
|
|
71
|
+
# machine.states
|
|
72
|
+
end
|
|
73
|
+
# delegate :machine, :to => :transition
|
|
74
|
+
|
|
75
|
+
def evaluate_with_arguments method_name_or_proc, *arguments
|
|
76
|
+
if method_name_or_proc.is_a?(Proc) && meth = method_name_or_proc
|
|
77
|
+
elsif meth = transition.machine.named_procs[method_name_or_proc]
|
|
78
|
+
elsif respond_to?( method_name_or_proc) && meth = method(method_name_or_proc)
|
|
79
|
+
elsif method_name_or_proc.to_s =~ /^not?_(.*)$/
|
|
80
|
+
# special case: prefix a method with no_ or not_ and get the
|
|
81
|
+
# boolean opposite of its evaluation result
|
|
82
|
+
return !( evaluate_with_arguments $1, *args )
|
|
83
|
+
else
|
|
84
|
+
raise NoMethodError.new( "undefined method_name `#{method_name_or_proc.to_s}' for \"#{__target__}\":#{__target__.class.to_s}" )
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if arguments.length < meth.arity.abs && meth.arity != -1
|
|
88
|
+
# ensure we don't have too few arguments
|
|
89
|
+
raise ArgumentError.new([meth.arity, arguments.length].inspect)
|
|
90
|
+
else
|
|
91
|
+
# ensure we don't pass too many arguments
|
|
92
|
+
arguments = arguments[0, meth.arity.abs]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# execute it!
|
|
96
|
+
__target__.with_methods_on(self) do
|
|
97
|
+
self.instance_exec *arguments, &meth
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def evaluate method_name_or_proc
|
|
102
|
+
arguments = [transition, args, __target__]
|
|
103
|
+
evaluate_with_arguments(method_name_or_proc, *arguments)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
alias_method :executioner_respond_to?, :respond_to?
|
|
107
|
+
|
|
108
|
+
def respond_to? method_name, include_private = false
|
|
109
|
+
executioner_respond_to?(method_name, include_private) ||
|
|
110
|
+
__target__.__send__( :respond_to?, method_name, include_private )
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
alias_method :executioner_method, :method
|
|
114
|
+
def method method_name
|
|
115
|
+
begin
|
|
116
|
+
executioner_method(method_name)
|
|
117
|
+
rescue NameError
|
|
118
|
+
__target__.__send__ :method, method_name
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Forwards any missing method call to the \target.
|
|
125
|
+
# TODO / FIXME / NOTE: we don't (can't ?) handle block arguments ...
|
|
126
|
+
def method_missing(method_name, *args)
|
|
127
|
+
if __target__.respond_to?(method_name, true)
|
|
128
|
+
begin
|
|
129
|
+
meth = __target__.__send__ :method, method_name
|
|
130
|
+
rescue NameError
|
|
131
|
+
super
|
|
132
|
+
end
|
|
133
|
+
__target__.instance_exec( *args, &meth)
|
|
134
|
+
else # let's hope it's a named proc
|
|
135
|
+
evaluate_with_arguments(method_name, *args)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# # Forwards any missing method call to the \target.
|
|
141
|
+
# def self.const_missing(const_name)
|
|
142
|
+
# unless __target__.class.const_defined?(const_name, true)
|
|
143
|
+
# super(const_name)
|
|
144
|
+
# end
|
|
145
|
+
# __target__.class.const_get(const_name)
|
|
146
|
+
# end
|
|
147
|
+
#
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/state_fu/hooks.rb
CHANGED
|
@@ -2,29 +2,34 @@ module StateFu
|
|
|
2
2
|
|
|
3
3
|
# TODO document structure / sequence of hooks elsewhere
|
|
4
4
|
|
|
5
|
-
module Hooks
|
|
5
|
+
module Hooks #:nodoc
|
|
6
6
|
|
|
7
|
-
ALL_HOOKS = [[:
|
|
8
|
-
[:
|
|
9
|
-
[:
|
|
10
|
-
[:
|
|
11
|
-
[:
|
|
12
|
-
[:
|
|
7
|
+
ALL_HOOKS = [[:machine, :before_all], # global before. prepare for any transition
|
|
8
|
+
[:event, :before], # prepare for the event
|
|
9
|
+
[:origin, :exit], # say goodbye!
|
|
10
|
+
[:event, :execute], # do stuff here for the event
|
|
11
|
+
[:target, :entry], # entry point. last chance to halt!
|
|
12
|
+
[:event, :after], # clean up after transition
|
|
13
|
+
[:target, :accepted], # state is changed. Do something about it.
|
|
14
|
+
[:machine, :after_all]] # global after. close up shop.
|
|
13
15
|
|
|
14
|
-
EVENT_HOOKS
|
|
15
|
-
STATE_HOOKS
|
|
16
|
-
|
|
16
|
+
EVENT_HOOKS = ALL_HOOKS.select { |type, name| type == :event }
|
|
17
|
+
STATE_HOOKS = ALL_HOOKS.select { |type, name| [:origin, :target].include?(type) }
|
|
18
|
+
MACHINE_HOOKS = ALL_HOOKS.select { |type, name| type == :machine }
|
|
19
|
+
HOOK_NAMES = ALL_HOOKS.map(&:last)
|
|
17
20
|
|
|
18
21
|
# just turn the above into what each class needs
|
|
19
22
|
# and make it into a nice hash: { :name =>[ hook, ... ], ... }
|
|
20
23
|
def self.for( me )
|
|
21
|
-
x = if me.is_a?(
|
|
22
|
-
elsif me.is_a?(
|
|
23
|
-
|
|
24
|
+
x = if me.is_a?(State); STATE_HOOKS
|
|
25
|
+
elsif me.is_a?(Event); EVENT_HOOKS
|
|
26
|
+
elsif me.is_a?(Machine); MACHINE_HOOKS
|
|
27
|
+
elsif me.is_a?(Sprocket); [] # let specs pass
|
|
28
|
+
else raise me
|
|
24
29
|
end.
|
|
25
|
-
map { |
|
|
26
|
-
hash = x.inject({}) {|h, a| h[a
|
|
27
|
-
hash.extend(
|
|
30
|
+
map { |type, name| [name, [].extend( OrderedHash )] }
|
|
31
|
+
hash = x.inject({}) {|h, a| h[a.first] = a.last; h }
|
|
32
|
+
hash.extend( OrderedHash ).freeze
|
|
28
33
|
end
|
|
29
34
|
|
|
30
35
|
end
|