davidlee-state-fu 0.0.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/LICENSE +40 -0
- data/README.textile +174 -0
- data/Rakefile +87 -0
- data/lib/no_stdout.rb +32 -0
- data/lib/state-fu.rb +93 -0
- data/lib/state_fu/binding.rb +262 -0
- data/lib/state_fu/core_ext.rb +23 -0
- data/lib/state_fu/event.rb +98 -0
- data/lib/state_fu/exceptions.rb +42 -0
- data/lib/state_fu/fu_space.rb +50 -0
- data/lib/state_fu/helper.rb +189 -0
- data/lib/state_fu/hooks.rb +28 -0
- data/lib/state_fu/interface.rb +139 -0
- data/lib/state_fu/lathe.rb +247 -0
- data/lib/state_fu/logger.rb +10 -0
- data/lib/state_fu/machine.rb +159 -0
- data/lib/state_fu/method_factory.rb +95 -0
- data/lib/state_fu/persistence/active_record.rb +27 -0
- data/lib/state_fu/persistence/attribute.rb +46 -0
- data/lib/state_fu/persistence/base.rb +98 -0
- data/lib/state_fu/persistence/session.rb +7 -0
- data/lib/state_fu/persistence.rb +50 -0
- data/lib/state_fu/sprocket.rb +27 -0
- data/lib/state_fu/state.rb +45 -0
- data/lib/state_fu/transition.rb +213 -0
- data/spec/helper.rb +86 -0
- data/spec/integration/active_record_persistence_spec.rb +189 -0
- data/spec/integration/class_accessor_spec.rb +127 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/ex_machine_for_accounts_spec.rb +79 -0
- data/spec/integration/example_01_document_spec.rb +127 -0
- data/spec/integration/example_02_string_spec.rb +87 -0
- data/spec/integration/instance_accessor_spec.rb +100 -0
- data/spec/integration/machine_duplication_spec.rb +95 -0
- data/spec/integration/requirement_reflection_spec.rb +201 -0
- data/spec/integration/sanity_spec.rb +31 -0
- data/spec/integration/state_definition_spec.rb +177 -0
- data/spec/integration/transition_spec.rb +1060 -0
- data/spec/spec.opts +7 -0
- data/spec/units/binding_spec.rb +145 -0
- data/spec/units/event_spec.rb +232 -0
- data/spec/units/exceptions_spec.rb +75 -0
- data/spec/units/fu_space_spec.rb +95 -0
- data/spec/units/lathe_spec.rb +567 -0
- data/spec/units/machine_spec.rb +237 -0
- data/spec/units/method_factory_spec.rb +359 -0
- data/spec/units/sprocket_spec.rb +71 -0
- data/spec/units/state_spec.rb +50 -0
- metadata +122 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
|
|
3
|
+
# unless Object.const_defined?('ActiveSupport')
|
|
4
|
+
#
|
|
5
|
+
# require 'active_support/core_ext/array'
|
|
6
|
+
# require 'active_support/core_ext/blank'
|
|
7
|
+
# require 'active_support/core_ext/class'
|
|
8
|
+
# require 'active_support/core_ext/module'
|
|
9
|
+
# require 'active_support/core_ext/hash/keys'
|
|
10
|
+
#
|
|
11
|
+
# class Hash #:nodoc:
|
|
12
|
+
# include ActiveSupport::CoreExtensions::Hash::Keys
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
|
|
16
|
+
class Symbol
|
|
17
|
+
unless instance_methods.include?(:'<=>')
|
|
18
|
+
# Logger.log ..
|
|
19
|
+
def <=> other
|
|
20
|
+
self.to_s <=> other.to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
class Event < StateFu::Sprocket
|
|
3
|
+
|
|
4
|
+
attr_reader :origins, :targets, :requirements
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# TODO - event guards
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
def initialize(machine, name, options={})
|
|
11
|
+
@requirements = [].extend ArrayWithSymbolAccessor
|
|
12
|
+
super( machine, name, options )
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def origin_names
|
|
16
|
+
origins ? origins.map(&:to_sym) : nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def target_names
|
|
20
|
+
targets ? targets.map(&:to_sym) : nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to?( state )
|
|
24
|
+
target_names.include?( state.to_sym )
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def from?( state )
|
|
28
|
+
origin_names.include?( state.to_sym )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def origins=( *args )
|
|
32
|
+
if [args].flatten == [:ALL]
|
|
33
|
+
@origins = machine.states
|
|
34
|
+
else
|
|
35
|
+
@origins = machine.find_or_create_states_by_name( *args.flatten ) #.extend( StateArray )
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def targets=( *args )
|
|
40
|
+
if [args].flatten == [:ALL]
|
|
41
|
+
@targets = machine.states
|
|
42
|
+
else
|
|
43
|
+
@targets = machine.find_or_create_states_by_name( *args.flatten ) # .extend( StateArray )
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# complete?(:origins) # do we have an origins?
|
|
48
|
+
# complete? # do we have an origins and targets?
|
|
49
|
+
def complete?( field = nil )
|
|
50
|
+
( field && [field] || [:origins, :targets] ).
|
|
51
|
+
map{ |s| send(s) }.
|
|
52
|
+
all?{ |f| !(f.nil? || f.empty?) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def origin
|
|
56
|
+
origins && origins.length == 1 && origins[0] || nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def target
|
|
60
|
+
targets && targets.length == 1 && targets[0] || nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def simple?
|
|
64
|
+
!! ( origins && target )
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def from *args
|
|
68
|
+
options = args.extract_options!.symbolize_keys!
|
|
69
|
+
args.flatten!
|
|
70
|
+
to = options.delete(:to)
|
|
71
|
+
if args.empty? && !to
|
|
72
|
+
if options.length == 1
|
|
73
|
+
self.origins= options.keys[0]
|
|
74
|
+
self.targets= options.values[0]
|
|
75
|
+
else
|
|
76
|
+
raise options.inspect
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
self.origins= *args
|
|
80
|
+
self.targets= to unless to.nil?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def to *args
|
|
85
|
+
options = args.extract_options!.symbolize_keys!
|
|
86
|
+
args.flatten!
|
|
87
|
+
raise options.inspect unless options.empty?
|
|
88
|
+
self.targets= *args
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def fireable_by?( binding )
|
|
92
|
+
requirements.reject do |r|
|
|
93
|
+
binding.evaluate_requirement( r )
|
|
94
|
+
end.empty?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
|
|
3
|
+
class Exception < ::Exception
|
|
4
|
+
attr_reader :binding, :options
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class RequirementError < Exception
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class TransitionHalted < Exception
|
|
11
|
+
attr_reader :transition
|
|
12
|
+
|
|
13
|
+
DEFAULT_MESSAGE = "The transition was halted"
|
|
14
|
+
|
|
15
|
+
def initialize( transition, message=DEFAULT_MESSAGE, options={})
|
|
16
|
+
@transition = transition
|
|
17
|
+
@options = options
|
|
18
|
+
super( message )
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class InvalidTransition < Exception
|
|
23
|
+
attr_reader :binding, :origin, :target, :event, :args
|
|
24
|
+
|
|
25
|
+
DEFAULT_MESSAGE = "An invalid transition was attempted"
|
|
26
|
+
|
|
27
|
+
def initialize( binding,
|
|
28
|
+
event,
|
|
29
|
+
origin,
|
|
30
|
+
target,
|
|
31
|
+
message=DEFAULT_MESSAGE,
|
|
32
|
+
options={})
|
|
33
|
+
@binding = binding
|
|
34
|
+
@event = event
|
|
35
|
+
@origin = origin
|
|
36
|
+
@target = target
|
|
37
|
+
@options = options
|
|
38
|
+
super( message )
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
# Provides a place to stash references.
|
|
3
|
+
# In most cases you won't need to access it directly, though
|
|
4
|
+
# calling reset! before each of your tests/specs can be helpful.
|
|
5
|
+
class FuSpace
|
|
6
|
+
cattr_reader :named_machines, :class_machines, :field_names
|
|
7
|
+
|
|
8
|
+
# class_machines[ Class ][ method_name ] # => a StateFu::Machine
|
|
9
|
+
# class_machines[ Klass ][ nil ] # => the Klass's default Machine
|
|
10
|
+
# field_names[ Class ][ method_name ] # => name of attribute / db field
|
|
11
|
+
|
|
12
|
+
# return the default machine, or an empty hash, given a missing index.
|
|
13
|
+
LAZY_HASH = lambda do |h, k|
|
|
14
|
+
if k.nil?
|
|
15
|
+
self[ StateFu::DEFAULT_MACHINE ]
|
|
16
|
+
else
|
|
17
|
+
h[k]= Hash.new()
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Add a machine to StateFu::FuSpace and register it with a given class, by a given name.
|
|
22
|
+
def self.insert!( klass, machine, name, field_name )
|
|
23
|
+
name = name.to_sym
|
|
24
|
+
field_name = field_name.to_sym
|
|
25
|
+
existing_machine = @@class_machines[klass][name]
|
|
26
|
+
if existing_machine && !existing_machine.empty?
|
|
27
|
+
raise("#{klass} already knows a non-empty Machine #{machine} by the name #{name}.")
|
|
28
|
+
else
|
|
29
|
+
@@class_machines[klass][name] = machine
|
|
30
|
+
@@field_names[klass][name] = field_name
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
class << self
|
|
34
|
+
alias_method :insert, :insert!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Clears all machines and their bindings to classes.
|
|
38
|
+
# Also initializes the hashes we use to store our references.
|
|
39
|
+
def self.beginners_mind!
|
|
40
|
+
@@named_machines = Hash.new
|
|
41
|
+
@@class_machines = Hash.new( &LAZY_HASH )
|
|
42
|
+
@@field_names = Hash.new( &LAZY_HASH )
|
|
43
|
+
end
|
|
44
|
+
class << self
|
|
45
|
+
alias_method :reset!, :beginners_mind!
|
|
46
|
+
alias_method :forget!, :beginners_mind!
|
|
47
|
+
end
|
|
48
|
+
beginners_mind!
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
|
|
3
|
+
# Utilities and snippets
|
|
4
|
+
module Helper
|
|
5
|
+
|
|
6
|
+
# Instance methods mixed in on inclusion of StateFu::Helper
|
|
7
|
+
module InstanceMethods
|
|
8
|
+
|
|
9
|
+
# if given a hash of options (or a splatted arglist containing
|
|
10
|
+
# one), merge them into @options. If given a block, eval it
|
|
11
|
+
# (yielding self if the block expects it)
|
|
12
|
+
def apply!( options={}, &block )
|
|
13
|
+
options.respond_to?(:keys) || options = options.extract_options!
|
|
14
|
+
@options.merge!( options.symbolize_keys! )
|
|
15
|
+
return self unless block_given?
|
|
16
|
+
case block.arity
|
|
17
|
+
when 1 # lambda{ |state| ... }
|
|
18
|
+
yield self
|
|
19
|
+
when -1, 0 # lambda{ } ( -1 in ruby 1.8.x but 0 in 1.9.x )
|
|
20
|
+
instance_eval &block
|
|
21
|
+
else
|
|
22
|
+
raise ArgumentError, "unexpected block arity: #{block.arity}"
|
|
23
|
+
end
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
alias_method :update!, :apply!
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Class methods mixed in on inclusion of StateFu::Helper
|
|
31
|
+
module ClassMethods
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.included( mod )
|
|
35
|
+
mod.send( :include, InstanceMethods )
|
|
36
|
+
mod.extend( ClassMethods )
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Stuff shared between StateArray and EventArray
|
|
42
|
+
module ArrayWithSymbolAccessor
|
|
43
|
+
# Pass a symbol to the array and get the object with that .name
|
|
44
|
+
# [<Foo @name=:bob>][:bob]
|
|
45
|
+
# => <Foo @name=:bob>
|
|
46
|
+
def []( idx )
|
|
47
|
+
begin
|
|
48
|
+
super( idx )
|
|
49
|
+
rescue TypeError => e
|
|
50
|
+
if idx.respond_to?(:to_sym)
|
|
51
|
+
self.detect { |i| i == idx || i.respond_to?(:name) && i.name == idx.to_sym }
|
|
52
|
+
else
|
|
53
|
+
raise e
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# so we can go Machine.states.names
|
|
59
|
+
# mildly helpful with irb + readline
|
|
60
|
+
def names
|
|
61
|
+
map(&:name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# SPECME
|
|
65
|
+
def except *syms
|
|
66
|
+
reject {|el| syms.flatten.compact.map(&:to_sym).include?(el.to_sym) } #.extend ArrayWithSymbolAccessor
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def only *syms
|
|
70
|
+
select {|el| syms.flatten.compact.map(&:to_sym).include?(el.to_sym) } #.extend ArrayWithSymbolAccessor
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Array extender. Used by Machine to keep a list of states.
|
|
76
|
+
module StateArray
|
|
77
|
+
include ArrayWithSymbolAccessor
|
|
78
|
+
|
|
79
|
+
# is there exactly one possible event to fire, with a single
|
|
80
|
+
# target event?
|
|
81
|
+
def next?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# if next?, return the state
|
|
85
|
+
def next
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Array extender. Used by Machine to keep a list of events.
|
|
91
|
+
module EventArray
|
|
92
|
+
include ArrayWithSymbolAccessor
|
|
93
|
+
|
|
94
|
+
# return all events transitioning from the given state
|
|
95
|
+
def from( origin )
|
|
96
|
+
select { |e| e.respond_to?(:from?) && e.from?( origin ) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# return all events transitioning to the given state
|
|
100
|
+
def to( target )
|
|
101
|
+
select { |e| e.respond_to?(:to?) && e.to?( target ) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# is there exactly one possible event to fire, with a single
|
|
105
|
+
# target event?
|
|
106
|
+
def next?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# if next?, return the event
|
|
110
|
+
def next
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Array extender. Used by Machine to keep a list of helpers to mix into
|
|
116
|
+
# context objects.
|
|
117
|
+
module HelperArray
|
|
118
|
+
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Extend an Array with this. It's a fairly compact implementation,
|
|
122
|
+
# though it won't be super fast with lots of elements.
|
|
123
|
+
# items. Internally objects are stored as a list of
|
|
124
|
+
# [:key, 'value'] pairs.
|
|
125
|
+
module OrderedHash
|
|
126
|
+
# if given a symbol / string, treat it as a key
|
|
127
|
+
def []( index )
|
|
128
|
+
begin
|
|
129
|
+
super( index )
|
|
130
|
+
rescue TypeError
|
|
131
|
+
( x = self.detect { |i| i.first == index }) && x[1]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# hash-style setter
|
|
136
|
+
def []=( index, value )
|
|
137
|
+
begin
|
|
138
|
+
super( index, value )
|
|
139
|
+
rescue TypeError
|
|
140
|
+
( x = self.detect { |i| i.first == index }) ?
|
|
141
|
+
x[1] = value : self << [ index, value ].extend( OrderedHash )
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# poor man's Hash.keys
|
|
146
|
+
def keys
|
|
147
|
+
map(&:first)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# poor man's Hash.values
|
|
151
|
+
def values
|
|
152
|
+
map(&:last)
|
|
153
|
+
end
|
|
154
|
+
end # OrderedHash
|
|
155
|
+
|
|
156
|
+
module ContextualEval
|
|
157
|
+
module InstanceMethods
|
|
158
|
+
def evaluate( &proc )
|
|
159
|
+
if proc.arity == 1
|
|
160
|
+
object.instance_exec( self, &proc )
|
|
161
|
+
else
|
|
162
|
+
instance_eval( &proc )
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def call_on_object_with_self( name )
|
|
167
|
+
# call a normal method on the object
|
|
168
|
+
# passing the transition as the argument if expected
|
|
169
|
+
if object.method(name).arity == 1
|
|
170
|
+
object.send( name, self )
|
|
171
|
+
else
|
|
172
|
+
object.send( name )
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def evaluate_named_proc_or_method( name )
|
|
177
|
+
if (name.is_a?( Proc ) && proc = name) || proc = machine.named_procs[ name ]
|
|
178
|
+
evaluate &proc
|
|
179
|
+
else
|
|
180
|
+
call_on_object_with_self( name )
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def self.included( klass )
|
|
186
|
+
klass.send :include, InstanceMethods
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
module Hooks
|
|
3
|
+
|
|
4
|
+
ALL_HOOKS = [[:event, :before], # good place to start a transaction, etc
|
|
5
|
+
[:origin, :exit], # say goodbye!
|
|
6
|
+
[:event, :execute], # do stuff here, as a rule of thumb
|
|
7
|
+
[:target, :entry], # last chance to halt!
|
|
8
|
+
[:event, :after], # clean up all the mess
|
|
9
|
+
[:target, :accepted]] # state changed. Quicksave!
|
|
10
|
+
|
|
11
|
+
EVENT_HOOKS = ALL_HOOKS.select { |type, name| type == :event }
|
|
12
|
+
STATE_HOOKS = ALL_HOOKS - EVENT_HOOKS
|
|
13
|
+
HOOK_NAMES = ALL_HOOKS.map {|a| a[1] }
|
|
14
|
+
|
|
15
|
+
# just turn the above into what each class needs
|
|
16
|
+
# and make it into a nice hash: { :name =>[ hook, ... ], ... }
|
|
17
|
+
def self.for( me )
|
|
18
|
+
x = if me.is_a?( StateFu::State ); STATE_HOOKS
|
|
19
|
+
elsif me.is_a?( StateFu::Event ); EVENT_HOOKS
|
|
20
|
+
else {}
|
|
21
|
+
end.
|
|
22
|
+
map { |_,name| [name, [].extend( StateFu::OrderedHash )] }
|
|
23
|
+
hash = x.inject({}) {|h, a| h[a[0]] = a[1] ; h}
|
|
24
|
+
hash.extend( StateFu::OrderedHash ).freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
module Interface
|
|
3
|
+
# Provides access to StateFu to your classes. Plenty of aliases are
|
|
4
|
+
# provided so you can use whatever makes sense to you.
|
|
5
|
+
module ClassMethods
|
|
6
|
+
|
|
7
|
+
# TODO:
|
|
8
|
+
# take option :alias => false (disable aliases) or :alias
|
|
9
|
+
# => :foo (use foo as class & instance accessor)
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Given no arguments, return the default machine (:state_fu) for the
|
|
13
|
+
# class, creating it if it did not exist.
|
|
14
|
+
#
|
|
15
|
+
# Given a symbol, return the machine by that name, creating it
|
|
16
|
+
# if it didn't exist.
|
|
17
|
+
#
|
|
18
|
+
# Given a block, also define it with the contents of the block.
|
|
19
|
+
#
|
|
20
|
+
# This can be done multiple times; changes are cumulative.
|
|
21
|
+
#
|
|
22
|
+
# You can have as many machines as you like per class.
|
|
23
|
+
#
|
|
24
|
+
# Klass.machine # the default machine named :om
|
|
25
|
+
# # equivalent to Klass.machine(:om)
|
|
26
|
+
# Klass.machine(:workflow) # another totally separate machine
|
|
27
|
+
#
|
|
28
|
+
# machine( name=:state_fu, options[:field_name], &block )
|
|
29
|
+
|
|
30
|
+
def machine( *args, &block )
|
|
31
|
+
options = args.extract_options!.symbolize_keys!
|
|
32
|
+
name = args[0] || StateFu::DEFAULT_MACHINE
|
|
33
|
+
StateFu::Machine.for_class( self, name, options, &block )
|
|
34
|
+
end
|
|
35
|
+
alias_method :stfu, :machine
|
|
36
|
+
alias_method :state_fu, :machine
|
|
37
|
+
alias_method :workflow, :machine
|
|
38
|
+
alias_method :statefully, :machine
|
|
39
|
+
alias_method :state_machine, :machine
|
|
40
|
+
alias_method :stateful, :machine
|
|
41
|
+
alias_method :workflow, :machine
|
|
42
|
+
alias_method :engine, :machine
|
|
43
|
+
|
|
44
|
+
# return a hash of :name => StateFu::Machine for your class.
|
|
45
|
+
def machines( *args, &block )
|
|
46
|
+
if args.empty? && !block_given?
|
|
47
|
+
StateFu::FuSpace.class_machines[self]
|
|
48
|
+
else
|
|
49
|
+
machine( *args, &block)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
alias_method :machines, :machines
|
|
53
|
+
alias_method :workflows, :machines
|
|
54
|
+
alias_method :engines, :machines
|
|
55
|
+
|
|
56
|
+
# return the list of machines names for this class
|
|
57
|
+
def machine_names()
|
|
58
|
+
StateFu::FuSpace.class_machines[self].keys
|
|
59
|
+
end
|
|
60
|
+
alias_method :machine_names, :machine_names
|
|
61
|
+
alias_method :workflow_names, :machine_names
|
|
62
|
+
alias_method :engine_names, :machine_names
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Give the gift of state to your objects. These methods
|
|
66
|
+
# grant access to StateFu::Binding objects, which are bundles of
|
|
67
|
+
# context linking a StateFu::Machine to an object / instance.
|
|
68
|
+
# Again, plenty of aliases are provided so you can use whatever
|
|
69
|
+
# makes sense to you.
|
|
70
|
+
module InstanceMethods
|
|
71
|
+
private
|
|
72
|
+
def _state_fu
|
|
73
|
+
@_state_fu ||= {}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# A StateFu::Binding comes into being, linking your object and a
|
|
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.
|
|
85
|
+
#
|
|
86
|
+
public
|
|
87
|
+
def _binding( name=StateFu::DEFAULT_MACHINE )
|
|
88
|
+
name = name.to_sym
|
|
89
|
+
if mach = StateFu::FuSpace.class_machines[self.class][name]
|
|
90
|
+
_state_fu[name] ||= StateFu::Binding.new( mach, self, name )
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
alias_method :fu, :_binding
|
|
95
|
+
alias_method :stfu, :_binding
|
|
96
|
+
alias_method :state_fu, :_binding
|
|
97
|
+
alias_method :stateful, :_binding
|
|
98
|
+
alias_method :workflow, :_binding
|
|
99
|
+
alias_method :engine, :_binding
|
|
100
|
+
alias_method :context, :_binding
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Gain awareness of all bindings (state contexts) this object
|
|
104
|
+
# has contemplated into being.
|
|
105
|
+
# Returns a Hash of { :name => <StateFu::Binding>, ... }
|
|
106
|
+
def _bindings()
|
|
107
|
+
_state_fu
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
alias_method :fus, :_bindings
|
|
111
|
+
alias_method :stfus, :_bindings
|
|
112
|
+
alias_method :state_fus, :_bindings
|
|
113
|
+
alias_method :state_foos, :_bindings
|
|
114
|
+
alias_method :workflows, :_bindings
|
|
115
|
+
alias_method :engines, :_bindings
|
|
116
|
+
alias_method :bindings, :_bindings
|
|
117
|
+
alias_method :machines, :_bindings # not strictly accurate, but makes sense sometimes
|
|
118
|
+
alias_method :contexts, :_bindings
|
|
119
|
+
|
|
120
|
+
# Instantiate bindings for all machines defined for this class.
|
|
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
|
+
def state_fu!( *names )
|
|
125
|
+
if [names || [] ].flatten!.map! {|n| n.to_sym }.empty?
|
|
126
|
+
names = self.class.machine_names()
|
|
127
|
+
end
|
|
128
|
+
@state_fu_initialized = true
|
|
129
|
+
names.map { |n| _binding( n ) }
|
|
130
|
+
end
|
|
131
|
+
alias_method :fu!, :state_fu!
|
|
132
|
+
alias_method :stfu!, :state_fu!
|
|
133
|
+
alias_method :state_fu!, :state_fu!
|
|
134
|
+
alias_method :init_machines!, :state_fu!
|
|
135
|
+
alias_method :initialize_state!, :state_fu!
|
|
136
|
+
alias_method :build_workflow!, :state_fu!
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|