state-fu 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +40 -0
- data/README.textile +293 -0
- data/Rakefile +114 -0
- data/lib/binding.rb +292 -0
- data/lib/event.rb +192 -0
- data/lib/executioner.rb +120 -0
- data/lib/hooks.rb +39 -0
- data/lib/interface.rb +132 -0
- data/lib/lathe.rb +538 -0
- data/lib/machine.rb +184 -0
- data/lib/method_factory.rb +243 -0
- data/lib/persistence.rb +116 -0
- data/lib/persistence/active_record.rb +34 -0
- data/lib/persistence/attribute.rb +47 -0
- data/lib/persistence/base.rb +100 -0
- data/lib/persistence/relaxdb.rb +23 -0
- data/lib/persistence/session.rb +7 -0
- data/lib/sprocket.rb +58 -0
- data/lib/state-fu.rb +56 -0
- data/lib/state.rb +48 -0
- data/lib/support/active_support_lite/array.rb +9 -0
- data/lib/support/active_support_lite/array/access.rb +60 -0
- data/lib/support/active_support_lite/array/conversions.rb +202 -0
- data/lib/support/active_support_lite/array/extract_options.rb +21 -0
- data/lib/support/active_support_lite/array/grouping.rb +109 -0
- data/lib/support/active_support_lite/array/random_access.rb +13 -0
- data/lib/support/active_support_lite/array/wrapper.rb +25 -0
- data/lib/support/active_support_lite/blank.rb +67 -0
- data/lib/support/active_support_lite/cattr_reader.rb +57 -0
- data/lib/support/active_support_lite/keys.rb +57 -0
- data/lib/support/active_support_lite/misc.rb +59 -0
- data/lib/support/active_support_lite/module.rb +1 -0
- data/lib/support/active_support_lite/module/delegation.rb +130 -0
- data/lib/support/active_support_lite/object.rb +9 -0
- data/lib/support/active_support_lite/string.rb +38 -0
- data/lib/support/active_support_lite/symbol.rb +16 -0
- data/lib/support/applicable.rb +41 -0
- data/lib/support/arrays.rb +197 -0
- data/lib/support/core_ext.rb +90 -0
- data/lib/support/exceptions.rb +106 -0
- data/lib/support/has_options.rb +16 -0
- data/lib/support/logger.rb +165 -0
- data/lib/support/methodical.rb +17 -0
- data/lib/support/no_stdout.rb +55 -0
- data/lib/support/plotter.rb +62 -0
- data/lib/support/vizier.rb +300 -0
- data/lib/tasks/spec_last.rake +55 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/transition.rb +338 -0
- data/lib/transition_query.rb +224 -0
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +118 -0
- 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 +47 -0
- data/spec/features/transition_boolean_comparison_spec.rb +101 -0
- data/spec/helper.rb +13 -0
- data/spec/integration/active_record_persistence_spec.rb +202 -0
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/class_accessor_spec.rb +117 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/example_01_document_spec.rb +133 -0
- data/spec/integration/example_02_string_spec.rb +88 -0
- data/spec/integration/instance_accessor_spec.rb +97 -0
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/machine_duplication_spec.rb +101 -0
- data/spec/integration/relaxdb_persistence_spec.rb +97 -0
- data/spec/integration/requirement_reflection_spec.rb +270 -0
- data/spec/integration/state_definition_spec.rb +163 -0
- data/spec/integration/transition_spec.rb +1033 -0
- data/spec/spec.opts +9 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +948 -0
- data/spec/units/binding_spec.rb +192 -0
- data/spec/units/event_spec.rb +214 -0
- data/spec/units/exceptions_spec.rb +82 -0
- data/spec/units/lathe_spec.rb +570 -0
- data/spec/units/machine_spec.rb +229 -0
- data/spec/units/method_factory_spec.rb +366 -0
- data/spec/units/sprocket_spec.rb +69 -0
- data/spec/units/state_spec.rb +59 -0
- metadata +171 -0
data/lib/event.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
module StateFu
|
2
|
+
class Event < StateFu::Sprocket
|
3
|
+
|
4
|
+
attr_reader :origins, :targets, :requirements, :sequence
|
5
|
+
|
6
|
+
# called by Lathe when a new event is constructed
|
7
|
+
def initialize(machine, name, options={})
|
8
|
+
@requirements = [].extend ArrayWithSymbolAccessor
|
9
|
+
@sequence = {}
|
10
|
+
super( machine, name, options )
|
11
|
+
end
|
12
|
+
|
13
|
+
# Sequences: pretending events are state-local -
|
14
|
+
# probably a bad idea but here for a "compatibility mode"
|
15
|
+
# with eg the activemodel state machine
|
16
|
+
|
17
|
+
# build a hash of target => [origins]
|
18
|
+
def add_to_sequence origin_states, target_state
|
19
|
+
origin_states = [origin_states].flatten
|
20
|
+
existing = origin_states.select {|s| target_for_origin(s) }
|
21
|
+
raise ArgumentError.new unless existing.empty? && !targets
|
22
|
+
@sequence[target_state] ||= []
|
23
|
+
[origin_states].flatten.each do |o|
|
24
|
+
@sequence[target_state] << o
|
25
|
+
end
|
26
|
+
@sequence
|
27
|
+
end
|
28
|
+
|
29
|
+
def sequence?
|
30
|
+
!sequence.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def target_for_origin origin_state
|
34
|
+
raise ArgumentError.new if origin_state.nil?
|
35
|
+
name = sequence.detect do |k,v|
|
36
|
+
v.include?(origin_state.to_sym)
|
37
|
+
end[0] rescue nil
|
38
|
+
machine.states[name] if name
|
39
|
+
end
|
40
|
+
|
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
|
+
# the names of all possible origin states
|
48
|
+
def origin_names
|
49
|
+
origins ? origins.map(&:to_sym) : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# the names of all possible target states
|
53
|
+
def target_names
|
54
|
+
targets ? targets.map(&:to_sym) : nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# tests if a state or state name is in the list of targets
|
58
|
+
def to?( state )
|
59
|
+
target_names.include?( state.to_sym )
|
60
|
+
end
|
61
|
+
|
62
|
+
# tests if a state or state name is in the list of origins
|
63
|
+
def from?( state )
|
64
|
+
origin_names.include?( state.to_sym ) || target_for_origin(state)
|
65
|
+
end
|
66
|
+
|
67
|
+
def cycle?
|
68
|
+
origin && (origin == target)
|
69
|
+
end
|
70
|
+
|
71
|
+
# *adds to* the origin states given a list of symbols / States
|
72
|
+
def origins=( *args )
|
73
|
+
update_state_collection( '@origins', *args )
|
74
|
+
end
|
75
|
+
|
76
|
+
# *adds to* the target states given a list of symbols / States
|
77
|
+
def targets=( *args )
|
78
|
+
update_state_collection( '@targets', *args )
|
79
|
+
end
|
80
|
+
|
81
|
+
# if there is a single state in #origins, returns it
|
82
|
+
def origin
|
83
|
+
origins && origins.length == 1 && origins[0] || nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# if there is a single state in #origins, returns it
|
87
|
+
def target
|
88
|
+
targets && targets.length == 1 && targets[0] || nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# a simple event has exactly one target, and any number of
|
92
|
+
# origins. It's simple because it can be triggered without
|
93
|
+
# supplying a target name - ie, <tt>go!<tt> vs <tt>go!(:home)<tt>
|
94
|
+
def simple?
|
95
|
+
!! ( origins && target || sequence? )
|
96
|
+
end
|
97
|
+
|
98
|
+
def fireable?( transition )
|
99
|
+
transition.valid?(true)
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
#
|
104
|
+
# Lathe methods
|
105
|
+
#
|
106
|
+
|
107
|
+
# adds an event requirement.
|
108
|
+
# DOCME // TODO - can this be removed?
|
109
|
+
def requires( *args, &block )
|
110
|
+
lathe.requires( *args, &block )
|
111
|
+
end
|
112
|
+
|
113
|
+
# generally called from a Lathe. Sets the origin(s) and optionally
|
114
|
+
# target(s) - that is, if you supply the :to option, or a single element
|
115
|
+
# hash of origins => targets ) of the event. Both origins= and
|
116
|
+
# targets= are accumulators.
|
117
|
+
def from *args
|
118
|
+
options = args.extract_options!.symbolize_keys!
|
119
|
+
args.flatten!
|
120
|
+
to = options.delete(:to) || options.delete(:transitions_to)
|
121
|
+
if args.empty? && !to
|
122
|
+
if options.length == 1
|
123
|
+
self.origins = options.keys[0]
|
124
|
+
self.targets = options.values[0]
|
125
|
+
else
|
126
|
+
raise options.inspect
|
127
|
+
end
|
128
|
+
else
|
129
|
+
self.origins = *args
|
130
|
+
self.targets = to unless to.nil?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# sets the target states for the event.
|
135
|
+
def to *args
|
136
|
+
options = args.extract_options!.symbolize_keys!
|
137
|
+
args.flatten!
|
138
|
+
raise options.inspect unless options.empty?
|
139
|
+
self.targets= *args
|
140
|
+
end
|
141
|
+
|
142
|
+
alias_method :transitions_to, :to
|
143
|
+
alias_method :transitions_from, :from
|
144
|
+
|
145
|
+
#
|
146
|
+
# misc
|
147
|
+
#
|
148
|
+
|
149
|
+
# display nice and short
|
150
|
+
def inspect
|
151
|
+
s = self.to_s
|
152
|
+
s = s[0,s.length-1]
|
153
|
+
display_hooks = hooks.dup
|
154
|
+
display_hooks.each do |k,v|
|
155
|
+
display_hooks.delete(k) if v.empty?
|
156
|
+
end
|
157
|
+
unless display_hooks.empty?
|
158
|
+
s << " hooks=#{display_hooks.inspect}"
|
159
|
+
end
|
160
|
+
unless requirements.empty?
|
161
|
+
s << " requirements=#{requirements.inspect}"
|
162
|
+
end
|
163
|
+
s << " targets=#{targets.map(&:to_sym).inspect}" if targets
|
164
|
+
s << " origins=#{origins.map(&:to_sym).inspect}" if origins
|
165
|
+
s << ">"
|
166
|
+
s
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
# internal method which accumulates states into an instance
|
172
|
+
# variable with successive invocations.
|
173
|
+
# ensures that calling #from multiple times adds to, rather than
|
174
|
+
# clobbering, the list of origins / targets.
|
175
|
+
def update_state_collection( ivar_name, *args)
|
176
|
+
raise ArgumentError if sequence?
|
177
|
+
new_states = if [args].flatten == [:ALL]
|
178
|
+
machine.states
|
179
|
+
else
|
180
|
+
machine.find_or_create_states_by_name( *args.flatten )
|
181
|
+
end
|
182
|
+
unless new_states.is_a?( Array )
|
183
|
+
new_states = [new_states]
|
184
|
+
end
|
185
|
+
existing = instance_variable_get( ivar_name )
|
186
|
+
# return existing if new_states.empty?
|
187
|
+
new_value = ((existing || [] ) + new_states).flatten.compact.uniq.extend( StateArray )
|
188
|
+
instance_variable_set( ivar_name, new_value )
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
end
|
data/lib/executioner.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module StateFu
|
2
|
+
#
|
3
|
+
# delegator class for evaluation methods / procs in the context of
|
4
|
+
# your object.
|
5
|
+
#
|
6
|
+
|
7
|
+
class Executioner
|
8
|
+
|
9
|
+
# give us a blank slate
|
10
|
+
# instance_methods.each { |m| undef_method m unless m =~ /(^__|^self|^nil\?$|^send$|proxy_|^object_id|^respond_to\?|^instance_exec|^instance_eval|^method$)/ }
|
11
|
+
|
12
|
+
def initialize transition, &block
|
13
|
+
@transition = transition
|
14
|
+
@__target__ = transition.object
|
15
|
+
@__self___ = self
|
16
|
+
yield self if block_given?
|
17
|
+
# forces method_missing to snap back to its pre-state-fu condition:
|
18
|
+
# @__target__.initialize_state_fu!
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
delegate :origin, :to => :transition, :prefix => true # transition_origin
|
23
|
+
delegate :target, :to => :transition, :prefix => true # transition_target
|
24
|
+
delegate :event, :to => :transition, :prefix => true # transition_event
|
25
|
+
|
26
|
+
delegate :halt!, :to => :transition
|
27
|
+
delegate :args, :to => :transition
|
28
|
+
delegate :options, :to => :transition
|
29
|
+
|
30
|
+
def binding
|
31
|
+
transition.binding
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :transition, :__target__, :__self__
|
35
|
+
|
36
|
+
alias_method :t, :transition
|
37
|
+
alias_method :current_transition, :transition
|
38
|
+
alias_method :context, :transition
|
39
|
+
alias_method :ctx, :transition
|
40
|
+
|
41
|
+
alias_method :arguments, :args
|
42
|
+
alias_method :transition_arguments, :args
|
43
|
+
|
44
|
+
def machine
|
45
|
+
binding.machine
|
46
|
+
end
|
47
|
+
|
48
|
+
def states
|
49
|
+
machine.states
|
50
|
+
end
|
51
|
+
# delegate :machine, :to => :transition
|
52
|
+
|
53
|
+
def evaluate_with_arguments method_name_or_proc, *arguments
|
54
|
+
if method_name_or_proc.is_a?(Proc) && meth = method_name_or_proc
|
55
|
+
elsif meth = transition.machine.named_procs[method_name_or_proc]
|
56
|
+
elsif respond_to?( method_name_or_proc) && meth = method(method_name_or_proc)
|
57
|
+
elsif method_name_or_proc.to_s =~ /^not?_(.*)$/
|
58
|
+
# special case: prefix a method with no_ or not_ and get the
|
59
|
+
# boolean opposite of its evaluation result
|
60
|
+
return !( evaluate_with_arguments $1, *args )
|
61
|
+
else
|
62
|
+
raise NoMethodError.new( "undefined method_name `#{method_name_or_proc.to_s}' for \"#{__target__}\":#{__target__.class.to_s}" )
|
63
|
+
end
|
64
|
+
|
65
|
+
if arguments.length < meth.arity.abs && meth.arity != -1
|
66
|
+
# ensure we don't have too few arguments
|
67
|
+
raise ArgumentError.new([meth.arity, arguments.length].inspect)
|
68
|
+
else
|
69
|
+
# ensure we don't pass too many arguments
|
70
|
+
arguments = arguments[0, meth.arity.abs]
|
71
|
+
end
|
72
|
+
|
73
|
+
# execute it!
|
74
|
+
__target__.with_methods_on(self) do
|
75
|
+
self.instance_exec *arguments, &meth
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def evaluate method_name_or_proc
|
80
|
+
arguments = [transition, args, __target__]
|
81
|
+
evaluate_with_arguments(method_name_or_proc, *arguments)
|
82
|
+
end
|
83
|
+
|
84
|
+
alias_method :executioner_respond_to?, :respond_to?
|
85
|
+
|
86
|
+
def respond_to? method_name, include_private = false
|
87
|
+
executioner_respond_to?(method_name, include_private) ||
|
88
|
+
__target__.__send__( :respond_to?, method_name, include_private )
|
89
|
+
end
|
90
|
+
|
91
|
+
alias_method :executioner_method, :method
|
92
|
+
def method method_name
|
93
|
+
begin
|
94
|
+
executioner_method(method_name)
|
95
|
+
rescue NameError
|
96
|
+
__target__.__send__ :method, method_name
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Forwards any missing method call to the \target.
|
103
|
+
# TODO / NOTE: we don't (can't ?) handle block arguments ...
|
104
|
+
def method_missing(method_name, *args)
|
105
|
+
if __target__.respond_to?(method_name, true)
|
106
|
+
begin
|
107
|
+
meth = __target__.__send__ :method, method_name
|
108
|
+
rescue NameError
|
109
|
+
super
|
110
|
+
end
|
111
|
+
__target__.instance_exec( *args, &meth)
|
112
|
+
else # let's hope it's a named proc
|
113
|
+
evaluate_with_arguments(method_name, *args)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
# NOTE: const_missing is not handled.
|
119
|
+
end
|
120
|
+
end
|
data/lib/hooks.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module StateFu
|
2
|
+
|
3
|
+
# TODO document structure / sequence of hooks elsewhere
|
4
|
+
|
5
|
+
module Hooks #:nodoc
|
6
|
+
|
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.
|
15
|
+
|
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)
|
20
|
+
|
21
|
+
# just turn the above into what each class needs
|
22
|
+
# and make it into a nice hash: { :name =>[ hook, ... ], ... }
|
23
|
+
def self.for( instance )
|
24
|
+
case instance
|
25
|
+
when State
|
26
|
+
STATE_HOOKS
|
27
|
+
when Event
|
28
|
+
EVENT_HOOKS
|
29
|
+
when Machine
|
30
|
+
MACHINE_HOOKS
|
31
|
+
when Sprocket
|
32
|
+
[]
|
33
|
+
end.
|
34
|
+
map { |type, name| [name, [].extend( OrderedHash )] }.
|
35
|
+
to_h.extend( OrderedHash ).freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/interface.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
module StateFu
|
2
|
+
module Interface
|
3
|
+
module SoftAlias
|
4
|
+
|
5
|
+
# define aliases that won't clobber existing methods -
|
6
|
+
# so we can be liberal with them.
|
7
|
+
def soft_alias(x)
|
8
|
+
aliases = [ x.to_a[0] ].flatten
|
9
|
+
original = aliases.shift
|
10
|
+
existing_method_names = (self.instance_methods | self.protected_instance_methods | self.private_instance_methods).map(&:to_sym)
|
11
|
+
taken, ok = aliases.partition { |a| existing_method_names.include?(a.to_sym) }
|
12
|
+
StateFu::Logger.debug("#{self.to_s} alias for ## #{original} already taken: #{taken.inspect}") unless taken.empty?
|
13
|
+
ok.each { |a| alias_method a, original}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
module Aliases
|
19
|
+
|
20
|
+
def self.extended(base)
|
21
|
+
base.extend SoftAlias
|
22
|
+
base.class_eval do
|
23
|
+
# instance method aliases
|
24
|
+
soft_alias :state_fu => [:stfu, :fu, :stateful, :workflow, :engine, :machine, :context]
|
25
|
+
soft_alias :state_fu_bindings => [:bindings, :workflows, :engines, :machines, :contexts]
|
26
|
+
soft_alias :state_fu! => [:stfu!, :initialize_machines!, :initialize_state!]
|
27
|
+
class << self
|
28
|
+
extend SoftAlias
|
29
|
+
# class method aliases
|
30
|
+
soft_alias :state_fu_machine => [:stfu, :state_fu, :workflow, :stateful, :statefully, :state_machine, :engine]
|
31
|
+
soft_alias :state_fu_machines => [:stfus, :state_fus, :workflows, :engines]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Provides access to StateFu to your classes. Plenty of aliases are
|
38
|
+
# provided so you can use whatever makes sense to you.
|
39
|
+
module ClassMethods
|
40
|
+
|
41
|
+
# TODO:
|
42
|
+
# take option :alias => false (disable aliases) or :alias
|
43
|
+
# => :foo (add :foo as class & instance accessor methods)
|
44
|
+
|
45
|
+
# Given no arguments, return the default machine (:state_fu) for the
|
46
|
+
# class, creating it if it did not exist.
|
47
|
+
#
|
48
|
+
# Given a symbol, return the machine by that name, creating it
|
49
|
+
# if it didn't exist, and definining it if a block is passed.
|
50
|
+
#
|
51
|
+
# Given a block, apply it to a StateFu::Lathe to define a
|
52
|
+
# machine, and return it.
|
53
|
+
#
|
54
|
+
# This can be done multiple times; changes are cumulative.
|
55
|
+
#
|
56
|
+
# You can have as many machines as you like per class.
|
57
|
+
#
|
58
|
+
# Klass.machine # the default machine named :om
|
59
|
+
# # equivalent to Klass.machine(:om)
|
60
|
+
# Klass.machine(:workflow) # another totally separate machine
|
61
|
+
#
|
62
|
+
# recognised options are:
|
63
|
+
# :field_name - specify the field to use for persistence.
|
64
|
+
# defaults to {machine_name}_field.
|
65
|
+
#
|
66
|
+
def state_fu_machine( *args, &block )
|
67
|
+
options = args.extract_options!.symbolize_keys!
|
68
|
+
name = args[0] || DEFAULT
|
69
|
+
StateFu::Machine.for_class( self, name, options, &block )
|
70
|
+
end
|
71
|
+
alias_method :machine, :state_fu_machine
|
72
|
+
|
73
|
+
def state_fu_field_names
|
74
|
+
@_state_fu_field_names ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def state_fu_machines
|
78
|
+
@_state_fu_machines ||= {}
|
79
|
+
end
|
80
|
+
alias_method :machines, :state_fu_machines
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
# These methods grant access to StateFu::Binding objects, which
|
85
|
+
# are bundles of context encapsulating a StateFu::Machine, an instance
|
86
|
+
# of a class, and its current state in the machine.
|
87
|
+
|
88
|
+
module InstanceMethods
|
89
|
+
|
90
|
+
def state_fu_bindings
|
91
|
+
@_state_fu_bindings ||= {}
|
92
|
+
end
|
93
|
+
|
94
|
+
# A StateFu::Binding comes into being when it is first referenced.
|
95
|
+
#
|
96
|
+
# This is the accessor method through which an object instance (or developer)
|
97
|
+
# can access a StateFu::Machine, the object's current state, the
|
98
|
+
# methods which trigger event transitions, etc.
|
99
|
+
|
100
|
+
def state_fu_binding( name = DEFAULT )
|
101
|
+
name = name.to_sym
|
102
|
+
if machine = self.class.state_fu_machines[name]
|
103
|
+
state_fu_bindings[name] ||= StateFu::Binding.new( machine, self, name )
|
104
|
+
else raise ArgumentError.new("No state machine called #{name} for #{self.class} #{self}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
alias_method :state_fu, :state_fu_binding
|
108
|
+
|
109
|
+
def current_state( name = DEFAULT )
|
110
|
+
state_fu_binding(name).current_state
|
111
|
+
end
|
112
|
+
|
113
|
+
def next!(name = DEFAULT, *args, &block )
|
114
|
+
state_fu_binding(name).next! *args, &block
|
115
|
+
end
|
116
|
+
alias_method :next_state!, :next!
|
117
|
+
alias_method :fire_next_transition!, :next!
|
118
|
+
|
119
|
+
# Instantiate bindings for all machines, which ensures that persistence
|
120
|
+
# fields are intialized and event methods defined.
|
121
|
+
# It's useful to call this before_create w/
|
122
|
+
# ActiveRecord classes, as this will cause the database field
|
123
|
+
# to be populated with the default state name.
|
124
|
+
|
125
|
+
def state_fu!
|
126
|
+
MethodFactory.define_singleton_method(self, :initialize_state_fu!) { true }
|
127
|
+
self.class.state_fu_machines.keys.map { |n| state_fu_binding( n ) }
|
128
|
+
end
|
129
|
+
|
130
|
+
end # ClassMethods
|
131
|
+
end # Interface
|
132
|
+
end # StateFu
|