davidlee-state-fu 0.0.2 → 0.2.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.
Files changed (42) hide show
  1. data/README.textile +13 -1
  2. data/lib/state-fu.rb +4 -3
  3. data/lib/state_fu/active_support_lite/array/access.rb +53 -0
  4. data/lib/state_fu/active_support_lite/array/conversions.rb +196 -0
  5. data/lib/state_fu/active_support_lite/array/extract_options.rb +20 -0
  6. data/lib/state_fu/active_support_lite/array/grouping.rb +106 -0
  7. data/lib/state_fu/active_support_lite/array/random_access.rb +12 -0
  8. data/lib/state_fu/active_support_lite/array/wrapper.rb +24 -0
  9. data/lib/state_fu/active_support_lite/array.rb +7 -0
  10. data/lib/state_fu/active_support_lite/blank.rb +58 -0
  11. data/lib/state_fu/active_support_lite/cattr_reader.rb +54 -0
  12. data/lib/state_fu/active_support_lite/inheritable_attributes.rb +1 -0
  13. data/lib/state_fu/active_support_lite/keys.rb +52 -0
  14. data/lib/state_fu/active_support_lite/object.rb +6 -0
  15. data/lib/state_fu/active_support_lite/string.rb +33 -0
  16. data/lib/state_fu/active_support_lite/symbol.rb +15 -0
  17. data/lib/state_fu/binding.rb +113 -58
  18. data/lib/state_fu/core_ext.rb +12 -13
  19. data/lib/state_fu/event.rb +4 -4
  20. data/lib/state_fu/exceptions.rb +12 -0
  21. data/lib/state_fu/helper.rb +74 -12
  22. data/lib/state_fu/lathe.rb +15 -0
  23. data/lib/state_fu/machine.rb +17 -25
  24. data/lib/state_fu/mock_transition.rb +38 -0
  25. data/lib/state_fu/persistence/active_record.rb +2 -2
  26. data/lib/state_fu/persistence/attribute.rb +4 -4
  27. data/lib/state_fu/persistence/base.rb +1 -1
  28. data/lib/state_fu/sprocket.rb +4 -0
  29. data/lib/state_fu/state.rb +4 -4
  30. data/lib/state_fu/transition.rb +24 -22
  31. data/spec/helper.rb +5 -3
  32. data/spec/integration/binding_extension_spec.rb +41 -0
  33. data/spec/integration/dynamic_requirement_spec.rb +160 -0
  34. data/spec/integration/example_01_document_spec.rb +2 -1
  35. data/spec/integration/lathe_extension_spec.rb +67 -0
  36. data/spec/integration/requirement_reflection_spec.rb +71 -11
  37. data/spec/integration/temp_spec.rb +17 -0
  38. data/spec/integration/transition_spec.rb +31 -19
  39. data/spec/units/binding_spec.rb +6 -0
  40. data/spec/units/event_spec.rb +6 -5
  41. data/spec/units/lathe_spec.rb +4 -2
  42. metadata +40 -17
@@ -0,0 +1,33 @@
1
+ class String
2
+ def underscore
3
+ self.gsub(/::/, '/').
4
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
5
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
6
+ tr("-", "_").
7
+ downcase
8
+ end
9
+
10
+ def demodulize
11
+ gsub(/^.*::/, '')
12
+ end
13
+
14
+ def constantize
15
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
16
+ raise NameError, "#{self} is not a valid constant name!"
17
+ end
18
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
19
+ end
20
+
21
+ def classify # DOES NOT SINGULARISE
22
+ camelize(self.sub(/.*\./, ''))
23
+ end
24
+
25
+ def camelize( first_letter_in_uppercase = true)
26
+ if first_letter_in_uppercase
27
+ gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
28
+ else
29
+ first + camelize(lower_case_and_underscored_word)[1..-1]
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,15 @@
1
+ unless :to_proc.respond_to?(:to_proc)
2
+ class Symbol
3
+ # Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
4
+ #
5
+ # # The same as people.collect { |p| p.name }
6
+ # people.collect(&:name)
7
+ #
8
+ # # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
9
+ # people.select(&:manager?).collect(&:salary)
10
+ def to_proc
11
+ Proc.new { |*args| args.shift.__send__(self, *args) }
12
+ end
13
+ end
14
+ end
15
+
@@ -17,11 +17,11 @@ module StateFu
17
17
  StateFu::Persistence.prepare_field( object.class, field_name )
18
18
  # add a persister
19
19
  @persister = StateFu::Persistence.for( self, field_name )
20
- Logger.info( "Persister added: #@persister ")
20
+ Logger.info( "Persister (#{@persister.class}) added: #{method_name} as field #{field_name}" )
21
21
 
22
22
  # define event methods on self( binding ) and @object
23
23
  StateFu::MethodFactory.new( self ).install!
24
-
24
+ machine.helpers.inject_into( self )
25
25
  # StateFu::Persistence.prepare_field( @object.class, field_name )
26
26
  end
27
27
  alias_method :o, :object
@@ -45,7 +45,11 @@ module StateFu
45
45
  alias_method :state, :current_state
46
46
 
47
47
  def current_state_name
48
- current_state.name
48
+ begin
49
+ current_state.name.to_sym
50
+ rescue NoMethodError
51
+ nil
52
+ end
49
53
  end
50
54
  alias_method :name, :current_state_name
51
55
  alias_method :state_name, :current_state_name
@@ -58,43 +62,40 @@ module StateFu
58
62
  alias_method :events_from_current_state, :events
59
63
 
60
64
  # the subset of events() whose requirements for firing are met
61
- def valid_events
65
+ def valid_events( *args )
62
66
  return nil unless current_state
63
- return [] unless current_state.exitable_by?( self )
64
- events.select {|e| e.fireable_by?( self ) }.extend EventArray
67
+ return [] unless current_state.exitable_by?( self, *args )
68
+ events.select {|e| e.fireable_by?( self, *args ) }.extend EventArray
65
69
  end
66
70
 
67
- def invalid_events
68
- (events - valid_events).extend StateArray
71
+ def invalid_events( *args )
72
+ ( events - valid_events( *args ) ).extend StateArray
69
73
  end
70
74
 
71
- def unmet_requirements_for(event, target)
75
+ def unmet_requirements_for( event, target )
72
76
  raise NotImplementedError
73
77
  end
74
78
 
75
79
  # the counterpart to valid_events - the states we can arrive at in
76
80
  # the firing of one event, taking into account event and state
77
81
  # transition requirements
78
- def valid_next_states
79
- valid_transitions.values.flatten.uniq.extend StateArray
82
+ def valid_next_states( *args )
83
+ vt = valid_transitions( *args )
84
+ vt && vt.values.flatten.uniq.extend( StateArray )
80
85
  end
81
86
 
82
87
  def next_states
83
88
  events.map(&:targets).compact.flatten.uniq.extend StateArray
84
89
  end
85
90
 
86
- def invalid_next_states
87
- states - valid_states
88
- end
89
-
90
91
  # returns a hash of { event => [states] } whose transition
91
92
  # requirements are met
92
- def valid_transitions
93
- h = {}
94
- return nil if valid_events.nil?
95
- valid_events.each do |e|
93
+ def valid_transitions( *args )
94
+ h = {}
95
+ return nil unless ve = valid_events( *args )
96
+ ve.each do |e|
96
97
  h[e] = e.targets.select do |s|
97
- s.enterable_by?( self )
98
+ s.enterable_by?( self, *args )
98
99
  end
99
100
  end
100
101
  h
@@ -105,8 +106,21 @@ module StateFu
105
106
  event, target = parse_destination( event_or_array )
106
107
  StateFu::Transition.new( self, event, target, *args, &block )
107
108
  end
108
- alias_method :fire, :transition
109
- alias_method :trigger, :transition
109
+ alias_method :fire, :transition
110
+ alias_method :fire_event, :transition
111
+ alias_method :trigger, :transition
112
+ alias_method :trigger_event, :transition
113
+ alias_method :begin_transition, :transition
114
+
115
+ def blank_mock_transition( *args, &block )
116
+ StateFu::MockTransition.new( self, nil, nil, *args, &block )
117
+ end
118
+
119
+ def mock_transition( event_or_array, *args, &block )
120
+ event, target = nil
121
+ event, target = parse_destination( event_or_array )
122
+ StateFu::MockTransition.new( self, event, target, *args, &block )
123
+ end
110
124
 
111
125
  # sanitize args for fire! and fireable?
112
126
  def parse_destination( event_or_array )
@@ -125,7 +139,7 @@ module StateFu
125
139
  end
126
140
 
127
141
  # check that the event and target are "valid" (all requirements met)
128
- def fireable?( event_or_array )
142
+ def fireable?( event_or_array, *args )
129
143
  event, target = parse_destination( event_or_array )
130
144
  begin
131
145
  t = transition( [event, target] )
@@ -134,11 +148,18 @@ module StateFu
134
148
  nil
135
149
  end
136
150
  end
137
- alias_method :event?, :fireable?
138
- alias_method :trigger?, :fireable?
139
- alias_method :triggerable?, :fireable?
140
- alias_method :transition?, :fireable?
141
- alias_method :transitionable?,:fireable?
151
+ alias_method :event?, :fireable?
152
+ alias_method :event_fireable?, :fireable?
153
+ alias_method :can_fire?, :fireable?
154
+ alias_method :can_fire_event?, :fireable?
155
+ alias_method :trigger?, :fireable?
156
+ alias_method :triggerable?, :fireable?
157
+ alias_method :can_trigger?, :fireable?
158
+ alias_method :can_trigger_event?, :fireable?
159
+ alias_method :event_triggerable?, :fireable?
160
+ alias_method :transition?, :fireable?
161
+ alias_method :can_transition?, :fireable?
162
+ alias_method :transitionable?, :fireable?
142
163
 
143
164
  # construct an event transition and fire it
144
165
  def fire!( event_or_array, *args, &block)
@@ -153,37 +174,53 @@ module StateFu
153
174
 
154
175
  # evaluate a requirement depending whether it's a method or proc,
155
176
  # and its arity - see helper.rb (ContextualEval) for the smarts
177
+
178
+ # TODO - enable requirement block / method to know the target
156
179
  def evaluate_requirement( name )
157
- evaluate_named_proc_or_method( name )
180
+ puts "DEPRECATED: evaluate_requirement #{name}"
181
+ evaluate_requirement_with_args( name )
158
182
  end
159
183
 
160
- def evaluate_requirement_message( name, dest )
161
- msg = machine.requirement_messages[name]
162
- if [String, NilClass].include?( msg.class )
163
- return msg
164
- else
165
- if dest.is_a?( StateFu::Transition )
166
- t = dest
167
- else
168
- event, target = parse_destination( event_or_array )
169
- t = transition( event, target )
170
- end
171
- case msg
172
- when Symbol
173
- t.evaluate_named_proc_or_method( msg )
174
- when Proc
175
- t.evaluate &msg
176
- end
177
- end
184
+ def evaluate_requirement_with_args( name, *args )
185
+ t = blank_mock_transition( *args )
186
+ evaluate_named_proc_or_method( name, t )
178
187
  end
179
188
 
189
+ def evaluate_requirement_with_transition( name, t )
190
+ evaluate_named_proc_or_method( name, t )
191
+ end
192
+
193
+ # TODO SPECME HACKERY CRUFT FIXME THISSUCKS and needs *args
194
+ # def evaluate_requirement_message( name, dest )
195
+ # # puts "#{name} #{dest}"
196
+ # msg = machine.requirement_messages[name]
197
+ # if [String, NilClass].include?( msg.class )
198
+ # return msg
199
+ # else
200
+ # if dest.is_a?( StateFu::Transition )
201
+ # t = dest
202
+ # else
203
+ # event, target = parse_destination( event_or_array )
204
+ # t = transition( event, target )
205
+ # end
206
+ # case msg
207
+ # when Symbol, Proc
208
+ # puts t.class
209
+ # evaluate_named_proc_or_method( msg, t )
210
+ # # when Proc
211
+ # # t.evaluate &msg
212
+ # end
213
+ # end
214
+ # end
215
+
180
216
  # if there is one simple event, return a transition for it
181
217
  # else return nil
182
218
  # TODO - not convinced about the method name / aliases - but next
183
219
  # is reserved :/
184
220
  def next_transition( *args, &block )
185
- return nil if valid_transitions.nil?
186
- next_transition_candidates = valid_transitions.select {|e, s| s.length == 1 }
221
+ vts = valid_transitions( *args )
222
+ return nil if vts.nil?
223
+ next_transition_candidates = vts.select {|e, s| s.length == 1 }
187
224
  if next_transition_candidates.length == 1
188
225
  nt = next_transition_candidates.first
189
226
  evt = nt[0]
@@ -192,11 +229,15 @@ module StateFu
192
229
  end
193
230
  end
194
231
 
195
- def next_state
196
- next_transition && next_transition.target
232
+ def next_state( *args )
233
+ nt = next_transition( *args )
234
+ nt && nt.target
197
235
  end
198
236
 
199
- alias_method :next_event, :next_transition
237
+ def next_event( *args )
238
+ nt = next_transition( *args )
239
+ nt && nt.event
240
+ end
200
241
 
201
242
  # if there is a next_transition, create, fire & return it
202
243
  # otherwise raise an InvalidTransition
@@ -205,10 +246,10 @@ module StateFu
205
246
  t.fire!
206
247
  t
207
248
  else
208
- n = valid_transitions && valid_transitions.length
249
+ vts = valid_transitions( *args )
250
+ n = vts && vts.length
209
251
  raise InvalidTransition.
210
- new( self, current_state, valid_transitions,
211
- "there are #{n} candidate transitions, need exactly 1")
252
+ new( self, current_state, vts, "there are #{n} candidate transitions, need exactly 1")
212
253
  end
213
254
  end
214
255
  alias_method :next_state!, :next!
@@ -247,15 +288,29 @@ module StateFu
247
288
 
248
289
  # if there is one possible cyclical event, evaluate its
249
290
  # requirements (true/false), else nil
250
- def cycle?
251
- if t = cycle
291
+ def cycle?( *args )
292
+ if t = cycle( *args )
252
293
  t.requirements_met?
253
294
  end
254
295
  end
255
296
 
256
297
  # display something sensible that doesn't take up the whole screen
257
298
  def inspect
258
- "#<#{self.class} ##{__id__} object_type=#{@object.class} method_name=#{method_name.inspect} field_name=#{persister.field_name.inspect} machine=#{@machine.inspect} options=#{options.inspect}>"
299
+ '|<= ' + self.class.to_s + ' ' +
300
+ attrs = [[:current_state, state_name.inspect],
301
+ [:object_type , @object.class],
302
+ [:method_name , method_name.inspect],
303
+ [:field_name , persister.field_name.inspect],
304
+ [:machine , machine.inspect]].
305
+ map {|x| x.join('=') }.join( " " ) + ' =>|'
306
+ end
307
+
308
+ def == other
309
+ if other.respond_to?(:to_sym) && current_state_name.is_a?(Symbol)
310
+ other.to_sym == current_state_name || super( other )
311
+ else
312
+ super( other )
313
+ end
259
314
  end
260
315
 
261
316
  end
@@ -1,18 +1,5 @@
1
1
  require 'rubygems'
2
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
3
  class Symbol
17
4
  unless instance_methods.include?(:'<=>')
18
5
  # Logger.log ..
@@ -21,3 +8,15 @@ class Symbol
21
8
  end
22
9
  end
23
10
  end
11
+
12
+ unless Object.const_defined?('ActiveSupport')
13
+ Dir[File.join(File.dirname( __FILE__), 'active_support_lite','**' )].sort.each do |lib|
14
+ next unless File.file?( lib )
15
+ require lib
16
+ end
17
+
18
+ class Hash #:nodoc:
19
+ include ActiveSupport::CoreExtensions::Hash::Keys
20
+ end
21
+
22
+ end
@@ -32,7 +32,7 @@ module StateFu
32
32
  if [args].flatten == [:ALL]
33
33
  @origins = machine.states
34
34
  else
35
- @origins = machine.find_or_create_states_by_name( *args.flatten ) #.extend( StateArray )
35
+ @origins = machine.find_or_create_states_by_name( *args.flatten ).extend( StateArray )
36
36
  end
37
37
  end
38
38
 
@@ -40,7 +40,7 @@ module StateFu
40
40
  if [args].flatten == [:ALL]
41
41
  @targets = machine.states
42
42
  else
43
- @targets = machine.find_or_create_states_by_name( *args.flatten ) # .extend( StateArray )
43
+ @targets = machine.find_or_create_states_by_name( *args.flatten ).extend( StateArray )
44
44
  end
45
45
  end
46
46
 
@@ -88,9 +88,9 @@ module StateFu
88
88
  self.targets= *args
89
89
  end
90
90
 
91
- def fireable_by?( binding )
91
+ def fireable_by?( binding, *args )
92
92
  requirements.reject do |r|
93
- binding.evaluate_requirement( r )
93
+ binding.evaluate_requirement_with_args( r, *args )
94
94
  end.empty?
95
95
  end
96
96
 
@@ -5,6 +5,18 @@ module StateFu
5
5
  end
6
6
 
7
7
  class RequirementError < Exception
8
+ attr_reader :transition
9
+ DEFAULT_MESSAGE = "The transition was halted"
10
+
11
+ def initialize( transition, message=DEFAULT_MESSAGE, options={})
12
+ @transition = transition
13
+ @options = options
14
+ super( message )
15
+ end
16
+
17
+ def inspect
18
+ "<StateFu::RequirementError #{message} #{@transition.origin.name}=[#{@transition.event.name}]=>#{transition.target.name}"
19
+ end
8
20
  end
9
21
 
10
22
  class TransitionHalted < Exception
@@ -112,10 +112,39 @@ module StateFu
112
112
 
113
113
  end
114
114
 
115
+ module ModuleRefArray
116
+ def modules
117
+ self.map do |h|
118
+ case h
119
+ when String, Symbol
120
+ Object.const_get( h.to_s.classify )
121
+ when Module
122
+ h
123
+ else
124
+ raise ArgumentError.new( h.class.inspect )
125
+ end
126
+ end
127
+ end # modules
128
+
129
+ def inject_into( obj )
130
+ metaclass = class << obj; self; end
131
+ mods = self.modules()
132
+ metaclass.class_eval do
133
+ mods.each do |mod|
134
+ include( mod )
135
+ end
136
+ end
137
+ end
138
+ end
139
+
115
140
  # Array extender. Used by Machine to keep a list of helpers to mix into
116
141
  # context objects.
117
142
  module HelperArray
143
+ include ModuleRefArray
144
+ end
118
145
 
146
+ module ToolArray
147
+ include ModuleRefArray
119
148
  end
120
149
 
121
150
  # Extend an Array with this. It's a fairly compact implementation,
@@ -155,29 +184,62 @@ module StateFu
155
184
 
156
185
  module ContextualEval
157
186
  module InstanceMethods
158
- def evaluate( &proc )
159
- if proc.arity == 1
160
- object.instance_exec( self, &proc )
187
+
188
+ # if we use &block syntax it stuffs the arity up, so we have to
189
+ # pass it as a normal argument
190
+ def limit_arguments( block, *args )
191
+ case block.arity
192
+ when -1, 0
193
+ nil
194
+ else
195
+ args[0 .. (block.arity - 1) ]
196
+ end
197
+ end
198
+
199
+ def evaluate( *args, &proc )
200
+ if proc.arity > 0
201
+ args = limit_arguments( proc, *args )
202
+ object.instance_exec( *args, &proc )
161
203
  else
162
204
  instance_eval( &proc )
163
205
  end
164
206
  end
165
207
 
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 )
208
+ def call_on_object_with_optional_args( name, *args )
209
+ if meth = object.method( name )
210
+ args = limit_arguments( meth, *args )
211
+ if args.nil?
212
+ object.send( name )
213
+ else
214
+ object.send( name, *args )
215
+ end
171
216
  else
172
- object.send( name )
217
+ raise NoMethodError.new( "undefined method #{name} for #{object.inspect}" )
173
218
  end
174
219
  end
175
220
 
176
- def evaluate_named_proc_or_method( name )
221
+ def call_on_object_with_self( name )
222
+ call_on_object_with_optional_args( name, self )
223
+ end
224
+
225
+ def evaluate_named_proc_or_method( name, *args )
177
226
  if (name.is_a?( Proc ) && proc = name) || proc = machine.named_procs[ name ]
178
- evaluate &proc
227
+ evaluate( *args, &proc )
228
+ else
229
+ call_on_object_with_optional_args( name, *args )
230
+ end
231
+ end
232
+
233
+ def find_event_target( evt, tgt )
234
+ case tgt
235
+ when StateFu::State
236
+ tgt
237
+ when Symbol
238
+ binding && binding.machine.states[ tgt ] # || raise( tgt.inspect )
239
+ when NilClass
240
+ evt.respond_to?(:target) && evt.target
179
241
  else
180
- call_on_object_with_self( name )
242
+ raise ArgumentError.new( "#{tgt.class} is not a Symbol, StateFu::State or nil (#{evt})" )
181
243
  end
182
244
  end
183
245
  end
@@ -9,6 +9,10 @@ module StateFu
9
9
  @machine = machine
10
10
  @sprocket = sprocket
11
11
  @options = options.symbolize_keys!
12
+
13
+ # extend ourself with any previously defined tools
14
+ machine.tools.inject_into( self )
15
+
12
16
  if sprocket
13
17
  sprocket.apply!( options )
14
18
  end
@@ -38,6 +42,10 @@ module StateFu
38
42
  !child?
39
43
  end
40
44
 
45
+ def master_lathe
46
+ machine.lathe
47
+ end
48
+
41
49
  # instantiate a child lathe and apply the given block
42
50
  def apply_to( sprocket, options, &block )
43
51
  StateFu::Lathe.new( machine, sprocket, options, &block )
@@ -116,6 +124,13 @@ module StateFu
116
124
  machine.helper *modules
117
125
  end
118
126
 
127
+ # helpers are mixed into all binding / transition contexts
128
+ def tool( *modules )
129
+ machine.tool *modules
130
+ # inject them into self for immediate use
131
+ modules.flatten.extend( ToolArray ).inject_into( self )
132
+ end
133
+
119
134
  #
120
135
  # event definition
121
136
  #
@@ -20,13 +20,14 @@ module StateFu
20
20
  ## Instance Methods
21
21
  ##
22
22
 
23
- attr_reader :states, :events, :options, :helpers, :named_procs, :requirement_messages
23
+ attr_reader :states, :events, :options, :helpers, :named_procs, :requirement_messages, :tools
24
24
 
25
25
  def initialize( name, options={}, &block )
26
26
  # TODO - name isn't actually used anywhere yet - remove from constructor
27
27
  @states = [].extend( StateArray )
28
28
  @events = [].extend( EventArray )
29
29
  @helpers = [].extend( HelperArray )
30
+ @tools = [].extend( ToolArray )
30
31
  @named_procs = {}
31
32
  @requirement_messages = {}
32
33
  @options = options
@@ -40,27 +41,15 @@ module StateFu
40
41
  alias_method :lathe, :apply!
41
42
 
42
43
  def helper_modules
43
- helpers.map do |h|
44
- case h
45
- when String, Symbol
46
- Object.const_get( h.to_s.classify )
47
- when Module
48
- h
49
- else
50
- raise ArgumentError.new( h.class.inspect )
51
- end
52
- end
44
+ helpers.modules
53
45
  end
54
46
 
55
47
  def inject_helpers_into( obj )
56
- metaclass = class << obj; self; end
48
+ helpers.inject_into( obj )
49
+ end
57
50
 
58
- mods = helper_modules()
59
- metaclass.class_eval do
60
- mods.each do |mod|
61
- include( mod )
62
- end
63
- end
51
+ def inject_tools_into( obj )
52
+ tools.inject_into( obj )
64
53
  end
65
54
 
66
55
  # the modules listed here will be mixed into Binding and
@@ -77,6 +66,11 @@ module StateFu
77
66
  modules_to_add.each { |mod| helpers << mod }
78
67
  end
79
68
 
69
+ # same deal but for extending Lathe
70
+ def tool *modules_to_add
71
+ modules_to_add.each { |mod| tools << mod }
72
+ end
73
+
80
74
  # make it so a class which has included StateFu has a binding to
81
75
  # this machine
82
76
  def bind!( klass, name=StateFu::DEFAULT_MACHINE, field_name = nil )
@@ -144,14 +138,12 @@ module StateFu
144
138
  end
145
139
  end
146
140
 
147
- def deep_copy()
148
- # use Marshal as a poor man's x-ray photocopier
149
- duplify = lambda { |o| Marshal.load Marshal.dump( o ) }
150
- returning Machine.new( nil, duplify.call( options )) do |m|
151
- @states.each { |s| m.states() << duplify.call(s) }
152
- @events.each { |e| m.events() << duplify.call(e) }
153
- end
141
+ # Marshal, the poor man's X-Ray photocopier.
142
+ # TODO: a version which will not break its teeth on procs
143
+ def deep_clone
144
+ Marshal::load(Marshal.dump(self))
154
145
  end
146
+ alias_method :deep_copy, :deep_clone
155
147
 
156
148
  def inspect
157
149
  "#<#{self.class} ##{__id__} states=#{state_names.inspect} events=#{event_names.inspect} options=#{options.inspect}>"
@@ -0,0 +1,38 @@
1
+ module StateFu
2
+ class MockTransition
3
+ include StateFu::Helper
4
+ include ContextualEval
5
+
6
+ attr_reader( :binding,
7
+ :machine,
8
+ :origin,
9
+ :target,
10
+ :event,
11
+ :args,
12
+ :errors,
13
+ :object,
14
+ :options,
15
+ :current_hook_slot,
16
+ :current_hook )
17
+
18
+ attr_accessor :test_only, :args, :options
19
+
20
+ def initialize( binding, event, target=nil, *args, &block )
21
+ @options = args.extract_options!.symbolize_keys!
22
+ if @binding = binding
23
+ @machine = binding.machine rescue nil
24
+ @object = binding.object rescue nil
25
+ @origin = binding.current_state rescue nil
26
+ end
27
+ @event = event
28
+ @target = find_event_target( event, target )
29
+ @args = args
30
+ @errors = []
31
+ @testing = true
32
+
33
+ machine.respond_to?(:inject_helpers_into) && machine.inject_helpers_into( self )
34
+ apply!( &block ) if block_given?
35
+ end
36
+
37
+ end
38
+ end