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.
Files changed (49) hide show
  1. data/LICENSE +40 -0
  2. data/README.textile +174 -0
  3. data/Rakefile +87 -0
  4. data/lib/no_stdout.rb +32 -0
  5. data/lib/state-fu.rb +93 -0
  6. data/lib/state_fu/binding.rb +262 -0
  7. data/lib/state_fu/core_ext.rb +23 -0
  8. data/lib/state_fu/event.rb +98 -0
  9. data/lib/state_fu/exceptions.rb +42 -0
  10. data/lib/state_fu/fu_space.rb +50 -0
  11. data/lib/state_fu/helper.rb +189 -0
  12. data/lib/state_fu/hooks.rb +28 -0
  13. data/lib/state_fu/interface.rb +139 -0
  14. data/lib/state_fu/lathe.rb +247 -0
  15. data/lib/state_fu/logger.rb +10 -0
  16. data/lib/state_fu/machine.rb +159 -0
  17. data/lib/state_fu/method_factory.rb +95 -0
  18. data/lib/state_fu/persistence/active_record.rb +27 -0
  19. data/lib/state_fu/persistence/attribute.rb +46 -0
  20. data/lib/state_fu/persistence/base.rb +98 -0
  21. data/lib/state_fu/persistence/session.rb +7 -0
  22. data/lib/state_fu/persistence.rb +50 -0
  23. data/lib/state_fu/sprocket.rb +27 -0
  24. data/lib/state_fu/state.rb +45 -0
  25. data/lib/state_fu/transition.rb +213 -0
  26. data/spec/helper.rb +86 -0
  27. data/spec/integration/active_record_persistence_spec.rb +189 -0
  28. data/spec/integration/class_accessor_spec.rb +127 -0
  29. data/spec/integration/event_definition_spec.rb +74 -0
  30. data/spec/integration/ex_machine_for_accounts_spec.rb +79 -0
  31. data/spec/integration/example_01_document_spec.rb +127 -0
  32. data/spec/integration/example_02_string_spec.rb +87 -0
  33. data/spec/integration/instance_accessor_spec.rb +100 -0
  34. data/spec/integration/machine_duplication_spec.rb +95 -0
  35. data/spec/integration/requirement_reflection_spec.rb +201 -0
  36. data/spec/integration/sanity_spec.rb +31 -0
  37. data/spec/integration/state_definition_spec.rb +177 -0
  38. data/spec/integration/transition_spec.rb +1060 -0
  39. data/spec/spec.opts +7 -0
  40. data/spec/units/binding_spec.rb +145 -0
  41. data/spec/units/event_spec.rb +232 -0
  42. data/spec/units/exceptions_spec.rb +75 -0
  43. data/spec/units/fu_space_spec.rb +95 -0
  44. data/spec/units/lathe_spec.rb +567 -0
  45. data/spec/units/machine_spec.rb +237 -0
  46. data/spec/units/method_factory_spec.rb +359 -0
  47. data/spec/units/sprocket_spec.rb +71 -0
  48. data/spec/units/state_spec.rb +50 -0
  49. metadata +122 -0
@@ -0,0 +1,247 @@
1
+ module StateFu
2
+ class Lathe
3
+
4
+ # NOTE: Sprocket is the abstract superclass of Event and State
5
+
6
+ attr_reader :machine, :sprocket, :options
7
+
8
+ def initialize( machine, sprocket = nil, options={}, &block )
9
+ @machine = machine
10
+ @sprocket = sprocket
11
+ @options = options.symbolize_keys!
12
+ if sprocket
13
+ sprocket.apply!( options )
14
+ end
15
+ if block_given?
16
+ if block.arity == 1
17
+ if sprocket
18
+ yield sprocket
19
+ else
20
+ raise ArgumentError
21
+ end
22
+ else
23
+ instance_eval( &block )
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # a 'child' lathe is created by apply_to, to deal with nested
31
+ # blocks for states / events ( which are sprockets )
32
+ def child?
33
+ !!@sprocket
34
+ end
35
+
36
+ # is this the toplevel lathe for a machine?
37
+ def master?
38
+ !child?
39
+ end
40
+
41
+ # instantiate a child lathe and apply the given block
42
+ def apply_to( sprocket, options, &block )
43
+ StateFu::Lathe.new( machine, sprocket, options, &block )
44
+ sprocket
45
+ end
46
+
47
+ # require that the current sprocket be of a given type
48
+ def require_sprocket( *valid_types )
49
+ raise ArgumentError.new("Lathe is for a #{sprocket.class}, not one of #{valid_types.inspect}") unless valid_types.include?( sprocket.class )
50
+ end
51
+
52
+ # ensure this is not a child lathe
53
+ def require_no_sprocket()
54
+ require_sprocket( NilClass )
55
+ end
56
+
57
+ # abstract method for defining states / events
58
+ def define_sprocket( type, name, options={}, &block )
59
+ name = name.to_sym
60
+ klass = StateFu.const_get((a=type.to_s.split('',2);[a.first.upcase, a.last].join))
61
+ collection = machine.send("#{type}s")
62
+ options.symbolize_keys!
63
+ if sprocket = collection[name]
64
+ apply_to( sprocket, options, &block )
65
+ sprocket
66
+ else
67
+ sprocket = klass.new( machine, name, options )
68
+ collection << sprocket
69
+ apply_to( sprocket, options, &block )
70
+ sprocket
71
+ end
72
+ end
73
+
74
+ def define_state( name, options={}, &block )
75
+ define_sprocket( :state, name, options, &block )
76
+ end
77
+
78
+ def define_event( name, options={}, &block )
79
+ define_sprocket( :event, name, options, &block )
80
+ end
81
+
82
+ def define_hook slot, method_name=nil, &block
83
+ unless sprocket.hooks.has_key?( slot )
84
+ raise ArgumentError, "invalid hook type #{slot.inspect} for #{sprocket.class}"
85
+ end
86
+ if block_given?
87
+ # unless (-1..1).include?( block.arity )
88
+ # raise ArgumentError, "unexpected block arity: #{block.arity}"
89
+ # end
90
+ case method_name
91
+ when Symbol
92
+ machine.named_procs[method_name] = block
93
+ hook = method_name
94
+ when NilClass
95
+ hook = block
96
+ # allow only one anonymous hook per slot in the interests of
97
+ # sanity - replace any pre-existing ones
98
+ sprocket.hooks[slot].delete_if { |h| Proc === h }
99
+ else
100
+ raise ArgumentError.new( method_name.inspect )
101
+ end
102
+ elsif method_name.is_a?( Symbol ) # no block
103
+ hook = method_name
104
+ # prevent duplicates
105
+ sprocket.hooks[slot].delete_if { |h| hook == h }
106
+ else
107
+ raise ArgumentError, "#{method_name.class} is not a symbol"
108
+ end
109
+ sprocket.hooks[slot] << hook
110
+ end
111
+
112
+ public
113
+
114
+ # helpers are mixed into all binding / transition contexts
115
+ def helper( *modules )
116
+ machine.helper *modules
117
+ end
118
+
119
+ #
120
+ # event definition
121
+ #
122
+
123
+ def event( name, options={}, &block )
124
+ options.symbolize_keys!
125
+ require_sprocket( StateFu::State, NilClass )
126
+ if child? && sprocket.is_a?( StateFu::State ) # in state block
127
+ targets = options.delete(:to)
128
+ evt = define_event( name, options, &block )
129
+ evt.from sprocket unless evt.origins
130
+ evt.to( targets ) unless targets.nil?
131
+ evt
132
+ else # in master lathe
133
+ origins = options.delete( :from )
134
+ targets = options.delete( :to )
135
+ evt = define_event( name, options, &block )
136
+ evt.from origins unless origins.nil?
137
+ evt.to targets unless targets.nil?
138
+ evt
139
+ end
140
+ end
141
+
142
+ def requires( *args, &block )
143
+ require_sprocket( StateFu::Event, StateFu::State )
144
+ options = args.extract_options!.symbolize_keys!
145
+ options.assert_valid_keys(:on, :message, :msg )
146
+ names = args
147
+ if block_given? && args.length > 1
148
+ raise ArgumentError.new("cannot supply a block for multiple requirements")
149
+ end
150
+ on = nil
151
+ names.each do |name|
152
+ raise ArgumentError.new( name.inspect ) unless name.is_a?( Symbol )
153
+ case sprocket
154
+ when StateFu::State
155
+ on ||= [(options.delete(:on) || [:entry])].flatten
156
+ sprocket.entry_requirements << name if on.include?( :entry )
157
+ sprocket.exit_requirements << name if on.include?( :exit )
158
+ when StateFu::Event
159
+ sprocket.requirements << name
160
+ end
161
+ if block_given?
162
+ machine.named_procs[name] = block
163
+ end
164
+ if msg = options.delete(:message) || options.delete(:msg)
165
+ # TODO - move this into machine
166
+ raise ArgumentError, msg.inspect unless [String, Symbol, Proc].include?(msg.class)
167
+ machine.requirement_messages[name] = msg
168
+ end
169
+ end
170
+ end
171
+ alias_method :must, :requires
172
+ alias_method :must_be, :requires
173
+ alias_method :needs, :requires
174
+ alias_method :satisfy, :requires
175
+ alias_method :must_satisfy, :requires
176
+
177
+ # create an event from *and* to the current state.
178
+ # Creates a loop, useful (only) for hooking behaviours onto.
179
+ def cycle( name=nil, options={}, &block )
180
+ name ||= "cycle_#{sprocket.name.to_s}"
181
+ require_sprocket( StateFu::State )
182
+ evt = define_event( name, options, &block )
183
+ evt.from sprocket
184
+ evt.to sprocket
185
+ evt
186
+ # raise NotImplementedError
187
+ end
188
+
189
+ #
190
+ # state definition
191
+ #
192
+
193
+ def initial_state( *args, &block )
194
+ require_no_sprocket()
195
+ machine.initial_state= state( *args, &block)
196
+ end
197
+
198
+ def state( name, options={}, &block )
199
+ require_no_sprocket()
200
+ define_state( name, options, &block )
201
+ end
202
+
203
+ def from *args, &block
204
+ require_sprocket( StateFu::Event )
205
+ sprocket.from( *args, &block )
206
+ end
207
+
208
+ def to *args, &block
209
+ require_sprocket( StateFu::Event )
210
+ sprocket.to( *args, &block )
211
+ end
212
+
213
+ #
214
+ # do something with all states / events
215
+ #
216
+ def each_sprocket( type, *args, &block)
217
+ require_no_sprocket()
218
+ options = args.extract_options!.symbolize_keys!
219
+ if args == [:ALL] || args == []
220
+ args = machine.send("#{type}s").except( options.delete(:except) )
221
+ end
222
+ args.map { |name| self.send( type, name, options.dup, &block) }.extend StateArray
223
+ end
224
+
225
+ def states( *args, &block )
226
+ each_sprocket( 'state', *args, &block )
227
+ end
228
+ alias_method :all_states, :states
229
+ alias_method :each_state, :states
230
+
231
+ def events( *args, &block )
232
+ each_sprocket( 'event', *args, &block )
233
+ end
234
+ alias_method :all_events, :events
235
+ alias_method :each_event, :events
236
+
237
+ # Bunch of silly little methods for defining events
238
+
239
+ def before *a, &b; require_sprocket( StateFu::Event ); define_hook :before, *a, &b; end
240
+ def on_exit *a, &b; require_sprocket( StateFu::State ); define_hook :exit, *a, &b; end
241
+ def execute *a, &b; require_sprocket( StateFu::Event ); define_hook :execute, *a, &b; end
242
+ def on_entry *a, &b; require_sprocket( StateFu::State ); define_hook :entry, *a, &b; end
243
+ def after *a, &b; require_sprocket( StateFu::Event ); define_hook :after, *a, &b; end
244
+ def accepted *a, &b; require_sprocket( StateFu::State ); define_hook :accepted, *a, &b; end
245
+
246
+ end
247
+ end
@@ -0,0 +1,10 @@
1
+ require 'logger'
2
+
3
+ module StateFu
4
+ if Object.const_defined?( "RAILS_DEFAULT_LOGGER" )
5
+ Logger = RAILS_DEFAULT_LOGGER
6
+ else
7
+ Logger = ::Logger.new( STDOUT )
8
+ Logger.level = ::Logger.const_get( (ENV["ZEN_LOGLEVEL"] || 'WARN').upcase )
9
+ end
10
+ end
@@ -0,0 +1,159 @@
1
+ module StateFu
2
+ class Machine
3
+ include Helper
4
+
5
+
6
+ # meta-constructor; expects to be called via Klass.machine()
7
+ def self.for_class(klass, name, options={}, &block)
8
+ options.symbolize_keys!
9
+ name = name.to_sym
10
+ unless machine = StateFu::FuSpace.class_machines[ klass ][ name ]
11
+ machine = new( name, options, &block )
12
+ machine.bind!( klass, name, options[:field_name] )
13
+ end
14
+ if block_given?
15
+ machine.apply!( &block )
16
+ end
17
+ machine
18
+ end
19
+
20
+ ##
21
+ ## Instance Methods
22
+ ##
23
+
24
+ attr_reader :states, :events, :options, :helpers, :named_procs, :requirement_messages, :name
25
+
26
+ def initialize( name, options={}, &block )
27
+ # TODO - @name isn't actually used anywhere yet except
28
+ # in deep_clone - remove it?
29
+ @name = name
30
+ @states = [].extend( StateArray )
31
+ @events = [].extend( EventArray )
32
+ @helpers = [].extend( HelperArray )
33
+ @named_procs = {}
34
+ @requirement_messages = {}
35
+ @options = options
36
+ end
37
+
38
+ def deep_clone()
39
+ m = Machine.new( name, options.dup )
40
+ end
41
+
42
+ # merge the commands in &block with the existing machine; returns
43
+ # a lathe for the machine.
44
+ def apply!( &block )
45
+ StateFu::Lathe.new( self, &block )
46
+ end
47
+ alias_method :lathe, :apply!
48
+
49
+ def helper_modules
50
+ helpers.map do |h|
51
+ case h
52
+ when String, Symbol
53
+ Object.const_get( h.to_s.classify )
54
+ when Module
55
+ h
56
+ else
57
+ raise ArgumentError.new( h.class.inspect )
58
+ end
59
+ end
60
+ end
61
+
62
+ def inject_helpers_into( obj )
63
+ metaclass = class << obj; self; end
64
+
65
+ mods = helper_modules()
66
+ metaclass.class_eval do
67
+ mods.each do |mod|
68
+ include( mod )
69
+ end
70
+ end
71
+ end
72
+
73
+ # the modules listed here will be mixed into Binding and
74
+ # Transition objects for this machine. use this to define methods,
75
+ # references or data useful to you during transitions, event
76
+ # hooks, or in general use of StateFu.
77
+ #
78
+ # They can be supplied as a string/symbol (as per rails controller
79
+ # helpers), or a Module.
80
+ #
81
+ # To do this globally, just duck-punch StateFu::Machine /
82
+ # StateFu::Binding.
83
+ def helper *modules_to_add
84
+ modules_to_add.each { |mod| helpers << mod }
85
+ end
86
+
87
+ # make it so a class which has included StateFu has a binding to
88
+ # this machine
89
+ def bind!( klass, name=StateFu::DEFAULT_MACHINE, field_name = nil )
90
+ field_name ||= name.to_s.underscore.tr(' ', '_') + StateFu::Persistence::DEFAULT_FIELD_NAME_SUFFIX
91
+ field_name = field_name.to_sym
92
+ StateFu::FuSpace.insert!( klass, self, name, field_name )
93
+ # define an accessor method with the given name
94
+ unless name == StateFu::DEFAULT_MACHINE
95
+ klass.class_eval do
96
+ define_method name do
97
+ state_fu( name )
98
+ end
99
+ end
100
+ end
101
+
102
+ # method_missing to catch NoMethodError for event methods, etc
103
+ StateFu::MethodFactory.prepare_class( klass )
104
+
105
+ # prepare the persistence field
106
+ StateFu::Persistence.prepare_field( klass, field_name )
107
+ true
108
+ end
109
+
110
+ def empty?
111
+ states.empty?
112
+ end
113
+
114
+ def initial_state=( s )
115
+ case s
116
+ when Symbol, String, StateFu::State
117
+ unless init_state = states[ s.to_sym ]
118
+ init_state = StateFu::State.new( self, s.to_sym )
119
+ states << init_state
120
+ end
121
+ @initial_state = init_state
122
+ else
123
+ raise( ArgumentError, s.inspect )
124
+ end
125
+ end
126
+
127
+ def initial_state()
128
+ @initial_state ||= states.first
129
+ end
130
+
131
+ def state_names
132
+ states.map(&:name)
133
+ end
134
+
135
+ def event_names
136
+ events.map(&:name)
137
+ end
138
+
139
+ # given a messy bunch of symbols, find or create a list of
140
+ # matching States.
141
+ def find_or_create_states_by_name( *args )
142
+ args = args.compact.flatten
143
+ raise ArgumentError.new( args.inspect ) unless args.all? { |a| [Symbol, StateFu::State].include? a.class }
144
+ args.map do |s|
145
+ unless state = states[s.to_sym]
146
+ # TODO clean this line up
147
+ state = s.is_a?( StateFu::State ) ? s : StateFu::State.new( self, s )
148
+ self.states << state
149
+ end
150
+ state
151
+ end
152
+ end
153
+
154
+ def inspect
155
+ "#<#{self.class} ##{__id__} states=#{state_names.inspect} events=#{event_names.inspect} options=#{options.inspect}>"
156
+ end
157
+
158
+ end
159
+ end
@@ -0,0 +1,95 @@
1
+ module StateFu
2
+ class MethodFactory
3
+
4
+ def initialize( _binding )
5
+ @binding = _binding
6
+ define_event_methods_on( _binding )
7
+ end
8
+
9
+ def install!
10
+ define_event_methods_on( @binding.object )
11
+ end
12
+
13
+ # ensure the methods are available before calling state_fu
14
+ def self.prepare_class( klass )
15
+ return if ( klass.instance_methods + klass.private_methods + klass.protected_methods ).map(&:to_sym).include?( :method_missing_before_state_fu )
16
+ klass.class_eval do
17
+ alias_method :method_missing_before_state_fu, :method_missing
18
+
19
+ def method_missing( method_name, *args, &block )
20
+ if @state_fu_initialized
21
+ method_missing_before_state_fu( method_name, *args, &block )
22
+ else
23
+ state_fu!
24
+ if respond_to?( method_name )
25
+ send( method_name, *args, &block )
26
+ else
27
+ method_missing_before_state_fu( method_name, *args, &block )
28
+ end
29
+ end
30
+ end # method_missing
31
+ end # class_eval
32
+ end # prepare_class
33
+
34
+ def define_method_on_metaclass( object, method_name, &block )
35
+ return false if object.respond_to?( method_name )
36
+ metaclass = class << object; self; end
37
+ metaclass.class_eval do
38
+ define_method( method_name, &block )
39
+ end
40
+ end
41
+
42
+ def define_event_methods_on( obj )
43
+ _binding = @binding
44
+ simple, complex = @binding.machine.events.partition(&:simple? )
45
+
46
+ # method definitions for simple events (only one possible target)
47
+ simple.each do |event|
48
+ # obj.event_name( *args )
49
+ # returns a new transition
50
+ method_name = event.name
51
+ define_method_on_metaclass( obj, method_name ) do |*args|
52
+ _binding.transition( event, *args )
53
+ end
54
+
55
+ # obj.event_name?()
56
+ # true if the event is fireable? (ie, requirements met)
57
+ method_name = "#{event.name}?"
58
+ define_method_on_metaclass( obj, method_name ) do
59
+ _binding.fireable?( event )
60
+ end
61
+
62
+ # obj.event_name!( *args )
63
+ # creates, fires and returns a transition
64
+ method_name = "#{event.name}!"
65
+ define_method_on_metaclass( obj, method_name ) do |*args|
66
+ _binding.fire!( event, *args )
67
+ end
68
+ end
69
+
70
+ # method definitions for complex events (target must be specified)
71
+ complex.each do |event|
72
+ # obj.event_name( target, *args )
73
+ # returns a new transition
74
+ define_method_on_metaclass( obj, event.name ) do |target, *args|
75
+ _binding.transition( [event, target], *args )
76
+ end
77
+
78
+ # obj.event_name?( target )
79
+ # true if the event is fireable? (ie, requirements met)
80
+ method_name = "#{event.name}?"
81
+ define_method_on_metaclass( obj, method_name ) do |target|
82
+ _binding.fireable?( [event, target] )
83
+ end
84
+
85
+ # obj.event_name!( target, *args )
86
+ # creates, fires and returns a transition
87
+ method_name = "#{event.name}!"
88
+ define_method_on_metaclass( obj, method_name ) do |target,*args|
89
+ _binding.fire!( [event, target], *args )
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,27 @@
1
+ module StateFu
2
+ module Persistence
3
+ class ActiveRecord < StateFu::Persistence::Base
4
+
5
+ def self.prepare_field( klass, field_name )
6
+ _field_name = field_name
7
+ klass.send :before_save, :state_fu!
8
+ # validates_presence_of _field_name
9
+
10
+ end
11
+
12
+ private
13
+
14
+ # We already checked that they exist, or we'd be using the
15
+ # Attribute version, so just do the simplest thing we can.
16
+
17
+ def read_attribute
18
+ object.send( :read_attribute, field_name )
19
+ end
20
+
21
+ def write_attribute( string_value )
22
+ object.send( :write_attribute, field_name, string_value )
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ module StateFu
2
+ module Persistence
3
+ class Attribute < StateFu::Persistence::Base
4
+
5
+ def self.prepare_field( klass, field_name )
6
+ # ensure getter exists
7
+ unless klass.instance_methods.include?( field_name )
8
+ Logger.info "Adding attr_reader :#{field_name} for #{klass}"
9
+ _field_name = field_name
10
+ klass.class_eval do
11
+ private
12
+ attr_reader _field_name
13
+ end
14
+ end
15
+
16
+ # ensure setter exists
17
+ unless klass.instance_methods.include?( "#{field_name}=" )
18
+ Logger.info "Adding attr_writer :#{field_name}= for #{klass}"
19
+ _field_name = field_name
20
+ klass.class_eval do
21
+ private
22
+ attr_writer _field_name
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ # Read / write our strings to a plain old instance variable
30
+ # Define it if it doesn't exist the first time we go to read it
31
+
32
+ def read_attribute
33
+ string = object.send( field_name )
34
+ Logger.info "Read attribute #{field_name}, got #{string} for #{object}"
35
+ string
36
+ end
37
+
38
+ def write_attribute( string_value )
39
+ writer_method = "#{field_name}="
40
+ Logger.info "Writing attribute #{field_name} -> #{string_value} for #{object}"
41
+ object.send( writer_method, string_value )
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,98 @@
1
+ module StateFu
2
+
3
+ class InvalidStateName < Exception
4
+ end
5
+
6
+ module Persistence
7
+ class Base
8
+
9
+ attr_reader :binding, :field_name, :current_state
10
+
11
+ def self.prepare_class( klass )
12
+ unless klass.instance_methods.include?( :method_missing_before_state_fu )
13
+ alias_method :method_missing_before_state_fu, :method_missing
14
+ klass.class_eval do
15
+ def method_missing( method_name, *args, &block )
16
+ state_fu!
17
+ begin
18
+ send( method_name, *args, &block )
19
+ rescue NoMethodError => e
20
+ method_missing_before_state_fu( method_name, *args, &block )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.prepare_field( klass, field_name )
28
+ raise NotImplementedError # abstract method
29
+ end
30
+
31
+ def initialize( binding, field_name )
32
+ @binding = binding
33
+ @field_name = field_name
34
+ @current_state = find_current_state()
35
+
36
+ if current_state.nil?
37
+ Logger.info("Object has an undefined state: #{object}")
38
+ Logger.info("Machine has no states: #{machine}") if machine.states.empty?
39
+ else
40
+ persist!
41
+ Logger.debug("Object resumes at #{current_state.name}: #{object}")
42
+ end
43
+ end
44
+
45
+ def find_current_state
46
+ string = read_attribute()
47
+ if string.blank?
48
+ machine.initial_state
49
+ else
50
+ state_name = string.to_sym
51
+ state = machine.states[ state_name ] || raise( StateFu::InvalidStateName, string )
52
+ end
53
+ end
54
+
55
+ def machine
56
+ binding.machine
57
+ end
58
+
59
+ def object
60
+ binding.object
61
+ end
62
+
63
+ def klass
64
+ object.class
65
+ end
66
+
67
+ # def method_name
68
+ # binding.method_name
69
+ # end
70
+
71
+ def current_state=( state )
72
+ raise(ArgumentError, state.inspect) unless state.is_a?(StateFu::State)
73
+ @current_state = state
74
+ persist!
75
+ end
76
+
77
+ def value()
78
+ @current_state && @current_state.name.to_s
79
+ end
80
+
81
+ def persist!
82
+ write_attribute( value() )
83
+ end
84
+
85
+ private
86
+
87
+ def read_attribute
88
+ raise "Abstract method! override me"
89
+ end
90
+
91
+ def write_attribute( string_value )
92
+ raise "Abstract method! override me"
93
+ end
94
+
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,7 @@
1
+ module StateFu
2
+ module Persistence
3
+ class Session
4
+ # Rails / etc session key
5
+ end
6
+ end
7
+ end