state-fu 0.11.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 (85) hide show
  1. data/LICENSE +40 -0
  2. data/README.textile +293 -0
  3. data/Rakefile +114 -0
  4. data/lib/binding.rb +292 -0
  5. data/lib/event.rb +192 -0
  6. data/lib/executioner.rb +120 -0
  7. data/lib/hooks.rb +39 -0
  8. data/lib/interface.rb +132 -0
  9. data/lib/lathe.rb +538 -0
  10. data/lib/machine.rb +184 -0
  11. data/lib/method_factory.rb +243 -0
  12. data/lib/persistence.rb +116 -0
  13. data/lib/persistence/active_record.rb +34 -0
  14. data/lib/persistence/attribute.rb +47 -0
  15. data/lib/persistence/base.rb +100 -0
  16. data/lib/persistence/relaxdb.rb +23 -0
  17. data/lib/persistence/session.rb +7 -0
  18. data/lib/sprocket.rb +58 -0
  19. data/lib/state-fu.rb +56 -0
  20. data/lib/state.rb +48 -0
  21. data/lib/support/active_support_lite/array.rb +9 -0
  22. data/lib/support/active_support_lite/array/access.rb +60 -0
  23. data/lib/support/active_support_lite/array/conversions.rb +202 -0
  24. data/lib/support/active_support_lite/array/extract_options.rb +21 -0
  25. data/lib/support/active_support_lite/array/grouping.rb +109 -0
  26. data/lib/support/active_support_lite/array/random_access.rb +13 -0
  27. data/lib/support/active_support_lite/array/wrapper.rb +25 -0
  28. data/lib/support/active_support_lite/blank.rb +67 -0
  29. data/lib/support/active_support_lite/cattr_reader.rb +57 -0
  30. data/lib/support/active_support_lite/keys.rb +57 -0
  31. data/lib/support/active_support_lite/misc.rb +59 -0
  32. data/lib/support/active_support_lite/module.rb +1 -0
  33. data/lib/support/active_support_lite/module/delegation.rb +130 -0
  34. data/lib/support/active_support_lite/object.rb +9 -0
  35. data/lib/support/active_support_lite/string.rb +38 -0
  36. data/lib/support/active_support_lite/symbol.rb +16 -0
  37. data/lib/support/applicable.rb +41 -0
  38. data/lib/support/arrays.rb +197 -0
  39. data/lib/support/core_ext.rb +90 -0
  40. data/lib/support/exceptions.rb +106 -0
  41. data/lib/support/has_options.rb +16 -0
  42. data/lib/support/logger.rb +165 -0
  43. data/lib/support/methodical.rb +17 -0
  44. data/lib/support/no_stdout.rb +55 -0
  45. data/lib/support/plotter.rb +62 -0
  46. data/lib/support/vizier.rb +300 -0
  47. data/lib/tasks/spec_last.rake +55 -0
  48. data/lib/tasks/state_fu.rake +57 -0
  49. data/lib/transition.rb +338 -0
  50. data/lib/transition_query.rb +224 -0
  51. data/spec/custom_formatter.rb +49 -0
  52. data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
  53. data/spec/features/method_missing_only_once_spec.rb +28 -0
  54. data/spec/features/not_requirements_spec.rb +118 -0
  55. data/spec/features/plotter_spec.rb +97 -0
  56. data/spec/features/shared_log_spec.rb +7 -0
  57. data/spec/features/singleton_machine_spec.rb +39 -0
  58. data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
  59. data/spec/features/transition_boolean_comparison_spec.rb +101 -0
  60. data/spec/helper.rb +13 -0
  61. data/spec/integration/active_record_persistence_spec.rb +202 -0
  62. data/spec/integration/binding_extension_spec.rb +41 -0
  63. data/spec/integration/class_accessor_spec.rb +117 -0
  64. data/spec/integration/event_definition_spec.rb +74 -0
  65. data/spec/integration/example_01_document_spec.rb +133 -0
  66. data/spec/integration/example_02_string_spec.rb +88 -0
  67. data/spec/integration/instance_accessor_spec.rb +97 -0
  68. data/spec/integration/lathe_extension_spec.rb +67 -0
  69. data/spec/integration/machine_duplication_spec.rb +101 -0
  70. data/spec/integration/relaxdb_persistence_spec.rb +97 -0
  71. data/spec/integration/requirement_reflection_spec.rb +270 -0
  72. data/spec/integration/state_definition_spec.rb +163 -0
  73. data/spec/integration/transition_spec.rb +1033 -0
  74. data/spec/spec.opts +9 -0
  75. data/spec/spec_helper.rb +132 -0
  76. data/spec/state_fu_spec.rb +948 -0
  77. data/spec/units/binding_spec.rb +192 -0
  78. data/spec/units/event_spec.rb +214 -0
  79. data/spec/units/exceptions_spec.rb +82 -0
  80. data/spec/units/lathe_spec.rb +570 -0
  81. data/spec/units/machine_spec.rb +229 -0
  82. data/spec/units/method_factory_spec.rb +366 -0
  83. data/spec/units/sprocket_spec.rb +69 -0
  84. data/spec/units/state_spec.rb +59 -0
  85. metadata +171 -0
@@ -0,0 +1,101 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ ##
4
+ ##
5
+ ##
6
+
7
+ describe "Copying / cloning a Machine" do
8
+
9
+ include MySpecHelper
10
+
11
+ describe "a shallow copy" do
12
+ before do
13
+ reset!
14
+ make_pristine_class("Klass")
15
+ @original = Klass.state_fu_machine do
16
+ state :a do
17
+ event :goto_b, :to => :b
18
+ end
19
+ end
20
+ @copy = @original.clone
21
+ end
22
+
23
+ # let's just test for strict object equality w/ equal? and call it a day
24
+
25
+ it "should update an event's options in the original when it's changed in the copy" do
26
+ @original.events[:goto_b].should be_equal @copy.events[:goto_b]
27
+ @original.events[:goto_b].options.should be_equal @copy.events[:goto_b].options
28
+ end
29
+
30
+ it "should update a state's options in the original when it's changed in the copy" do
31
+ @original.states[:a].should be_equal @copy.states[:a]
32
+ @original.states[:a].options.should be_equal @copy.states[:a].options
33
+ end
34
+
35
+ it "should update the original when an event is added to the clone" do
36
+ @original.events.should be_equal @copy.events
37
+ end
38
+
39
+ it "should update the original when a state is added to the clone" do
40
+ @original.states.should be_equal @copy.states
41
+ end
42
+
43
+ it "should update the original with any changes to helpers" do
44
+ @original.helpers.should be_equal @copy.helpers
45
+ end
46
+
47
+ it "should update the original with any changes to named_procs" do
48
+ @original.named_procs.should be_equal @copy.named_procs
49
+ end
50
+
51
+ it "should update the original with any changes to requirement_messages" do
52
+ @original.requirement_messages.should be_equal @copy.requirement_messages
53
+ end
54
+
55
+ end # shallow
56
+
57
+ describe "a deep copy" do
58
+ before do
59
+ reset!
60
+ make_pristine_class("Klass")
61
+ @original = Klass.state_fu_machine do
62
+ state :a do
63
+ event :goto_b, :to => :b
64
+ end
65
+ end
66
+ @copy = @original.deep_copy()
67
+ end
68
+
69
+ it "should NOT update an event's options in the original when it's changed in the copy" do
70
+ @original.events[:goto_b].should_not be_equal @copy.events[:goto_b]
71
+ @original.events[:goto_b].options.should_not be_equal @copy.events[:goto_b].options
72
+ end
73
+
74
+ it "should NOT update a state's options in the original when it's changed in the copy" do
75
+ @original.states[:a].should_not be_equal @copy.states[:a]
76
+ @original.states[:a].options.should_not be_equal @copy.states[:a].options
77
+ end
78
+
79
+ it "should NOT update the original when an event is added to the clone" do
80
+ @original.events.should_not be_equal @copy.events
81
+ end
82
+
83
+ it "should NOT update the original when a state is added to the clone" do
84
+ @original.states.should_not be_equal @copy.states
85
+ end
86
+
87
+ it "should NOT update the original with any changes to helpers" do
88
+ @original.helpers.should_not be_equal @copy.helpers
89
+ end
90
+
91
+ it "should NOT update the original with any changes to named_procs" do
92
+ @original.named_procs.should_not be_equal @copy.named_procs
93
+ end
94
+
95
+ it "should NOT update the original with any changes to requirement_messages" do
96
+ @original.requirement_messages.should_not be_equal @copy.requirement_messages
97
+ end
98
+
99
+ end # deep
100
+
101
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe "a RelaxDB::Document's persister" do
4
+
5
+ include MySpecHelper
6
+ before(:all) do
7
+ prepare_relaxdb()
8
+ end
9
+
10
+ before(:each) do
11
+ if skip_slow_specs?
12
+ skip_slow_specs and return false
13
+ end
14
+ skip_unless_relaxdb()
15
+ reset!
16
+ make_pristine_class( 'ExampleDoc', RelaxDB::Document )
17
+ end
18
+
19
+ it "should be a subclass of RelaxDB::Document" do
20
+ ExampleDoc.superclass.should == RelaxDB::Document
21
+ end
22
+
23
+ describe "when no machine is defined" do
24
+ end
25
+
26
+ describe "when the :field_name is a RelaxDB property" do
27
+ before do
28
+ ExampleDoc.class_eval do
29
+ property :property_field
30
+ state_fu_machine :field_name => "property_field" do
31
+ # ...
32
+ end
33
+ end
34
+ @obj = ExampleDoc.new
35
+ end
36
+
37
+ it "should add a relaxdb persister" do
38
+ @obj.state_fu.persister.class.should == StateFu::Persistence::RelaxDB
39
+ end
40
+ end
41
+
42
+ describe "when the :field_name is not a RelaxDB property" do
43
+ before do
44
+ ExampleDoc.class_eval do
45
+ state_fu_machine :field_name => "not_a_property" do
46
+ # ...
47
+ end
48
+ end
49
+ @obj = ExampleDoc.new
50
+ end
51
+ it "should add an attribute-based persister" do
52
+ @obj.state_fu.persister.class.should == StateFu::Persistence::Attribute
53
+ end
54
+ end
55
+ end
56
+
57
+ describe StateFu::Persistence::RelaxDB do
58
+ before(:all) do
59
+ prepare_relaxdb()
60
+ end
61
+
62
+ include MySpecHelper
63
+ describe "a RelaxDB::Document with a simple machine" do
64
+ before do
65
+ skip_unless_relaxdb()
66
+ reset!
67
+ make_pristine_class( 'ExampleDoc', RelaxDB::Document )
68
+ ExampleDoc.class_eval do
69
+ property :state_fu_field
70
+ state_fu_machine do
71
+ state :hungry do
72
+ event :eat, :to => :satiated
73
+ end
74
+ end # machine
75
+ end # class_eval
76
+ @obj = ExampleDoc.new
77
+ end # before
78
+
79
+ it "should update the property on transition acceptance" do
80
+ @obj.state_fu.should == :hungry
81
+ t = @obj.eat!
82
+ t.should be_accepted
83
+ @obj.state_fu.should == :satiated
84
+ @obj.send(:state_fu_field).should == 'satiated'
85
+ end
86
+
87
+ it "should persist the current state of the machine to the database" do
88
+ @obj.state_fu.should == :hungry
89
+ @obj.eat!
90
+ @obj.state_fu.should == :satiated
91
+ @obj.save!
92
+ @obj2 = RelaxDB.load( @obj._id )
93
+ @obj2.state_fu.should == :satiated
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,270 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+
4
+ describe "Transition requirement reflection" do
5
+ include MySpecHelper
6
+
7
+ before do
8
+ reset!
9
+ make_pristine_class("Klass") do
10
+ def turban?; false end
11
+ def arrest_warrant?; false end
12
+ def papers_in_order?; true end
13
+ def papers_in_order?; true end
14
+ def money_for_bribe?; true end
15
+ def spacesuit?; true end
16
+ def plane_ticket?; true end
17
+ def fuel?; true end
18
+ end
19
+ @machine = Klass.state_fu_machine do
20
+ state :soviet_russia do
21
+ requires( :papers_in_order?, :on => [:entry, :exit] )
22
+ requires( :money_for_bribe?, :on => [:entry, :exit] )
23
+ end
24
+
25
+ state :america do
26
+ requires( :no_turban?,
27
+ :us_visa?,
28
+ :on => :entry )
29
+ requires( :no_arrest_warrant?, :on => [:entry,:exit] )
30
+ end
31
+
32
+ state :moon do
33
+ requires :spacesuit?
34
+ end
35
+
36
+ event( :catch_plane,
37
+ :from => states.except(:moon),
38
+ :to => states.except(:moon) ) do
39
+ requires :plane_ticket?
40
+ end
41
+
42
+ event( :fly_spaceship,
43
+ :from => :ALL,
44
+ :to => :ALL ) do
45
+ requires :fuel?
46
+ end
47
+
48
+ end # machine
49
+ @obj = Klass.new()
50
+ end # before
51
+
52
+ describe "transition.valid? / transition.requirements_met?" do
53
+ it "should be true if all requirements are met (return truth)" do
54
+ #@obj.state_fu.next_states[:moon].entry_requirements.should == [:spacesuit?]
55
+
56
+ #@obj.state_fu.fireable?([:fly_spaceship,:moon]).should == true
57
+ @obj.can_fly_spaceship?(:moon).should == true
58
+ #@obj.fly_spaceship(:moon).requirements_met?.should == true
59
+ #@obj.fly_spaceship(:moon).should be_valid
60
+ end
61
+
62
+ it "should be false if not all requirements are met" do
63
+ stub( @obj ).spacesuit?() { false }
64
+ @obj.state_fu.next_states[:moon].entry_requirements.should == [:spacesuit?]
65
+ # @obj.state_fu.evaluate(:spacesuit?).should == false
66
+ @obj.can_fly_spaceship?(:moon).should == false
67
+ @obj.fly_spaceship(:moon).requirements_met?.should == false
68
+ @obj.fly_spaceship(:moon).should_not be_valid
69
+ end
70
+ end
71
+
72
+ describe "flying from russia to america without one's affairs in order while wearing a turban" do
73
+ before do
74
+ mock( @obj ).us_visa?(anything) { false }
75
+ mock( @obj ).no_turban?(anything) { false }
76
+ mock( @obj ).no_arrest_warrant?(anything) { false }
77
+ mock( @obj ).money_for_bribe?(anything) { false }
78
+ mock( @obj ).papers_in_order?(anything) { false }
79
+ end
80
+
81
+ describe "when no messages are supplied for the requirements" do
82
+ describe "given transition.unmet_requirements" do
83
+ it "should contain a list of failing requirement names as symbols" do
84
+ @obj.state_fu.catch_plane(:america).unmet_requirements.should == [ :papers_in_order?,
85
+ :money_for_bribe?,
86
+ :no_turban?,
87
+ :us_visa?,
88
+ :no_arrest_warrant? ]
89
+ end
90
+ end # unmet requirements
91
+
92
+ describe "given transition.unmet_requirement_messages" do
93
+ it "should return a list of symbols" do
94
+ @obj.state_fu.catch_plane(:america).unmet_requirement_messages.should ==
95
+ [:papers_in_order?, :money_for_bribe?, :no_turban?, :us_visa?, :no_arrest_warrant?]
96
+ end
97
+ end # unmet_requirement_messages
98
+ end
99
+
100
+ describe "when a message is supplied for the money_for_bribe? entry requirement" do
101
+ before do
102
+ Klass.state_fu_machine do
103
+ state :soviet_russia do
104
+ requires( :money_for_bribe?, :message => "This guard is thirsty! Do you have anything to declare?" )
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "given transition.unmet_requirements" do
110
+ it "should still contain a list of failing requirement names as symbols" do
111
+ @obj.state_fu.catch_plane(:america).unmet_requirements.should == [ :papers_in_order?,
112
+ :money_for_bribe?,
113
+ :no_turban?,
114
+ :us_visa?,
115
+ :no_arrest_warrant? ]
116
+ end
117
+ end
118
+
119
+ describe "given transition.unmet_requirement_messages" do
120
+ it "should contain a list of nils plus the requirement message for money_for_bribe? as a string" do
121
+ @obj.state_fu.catch_plane(:america).unmet_requirement_messages.should ==
122
+ [:papers_in_order?,
123
+ "This guard is thirsty! Do you have anything to declare?",
124
+ :no_turban?,
125
+ :us_visa?,
126
+ :no_arrest_warrant?]
127
+ end
128
+ end
129
+
130
+ end
131
+ end # flying with a turban
132
+
133
+ describe "transition.unmet_requirements" do
134
+ it "should be empty when all requirements are met" do
135
+ @obj.state_fu.fly_spaceship(:moon).unmet_requirements.should == []
136
+ end
137
+
138
+ describe "when a message is supplied for the requirement" do
139
+ it "should contain a list of the requirement failure messages as strings" do
140
+ mock( @obj ).spacesuit?(anything) { false }
141
+ mock( @obj ).fuel?(anything) { false }
142
+ @obj.state_fu.fly_spaceship(:moon).unmet_requirements.should == [:spacesuit?, :fuel?]
143
+ end
144
+ end
145
+ end
146
+
147
+
148
+ describe "transition.unmet_requirement_messages" do
149
+ describe "when a string message is defined for one of two unmet_requirements" do
150
+ before do
151
+ stub( @obj ).spacesuit?() { false }
152
+ stub( @obj ).fuel?() { false }
153
+ @msg = "You got no spacesuit."
154
+ @machine.requirement_messages[:spacesuit?] = @msg
155
+ end
156
+
157
+ it "should return an array with the requirement message and nil" do
158
+ t = @obj.state_fu.fly_spaceship(:moon)
159
+ t.unmet_requirements.length.should == 2
160
+ messages = t.unmet_requirement_messages
161
+ messages.should be_kind_of( Array )
162
+ messages.length.should == 2
163
+ messages.strings.length.should == 1
164
+ messages.symbols.length.should == 1
165
+ messages.strings.first.should == @msg
166
+ messages.symbols.first.should == :fuel?
167
+ end
168
+ end
169
+
170
+ describe "when a proc message is defined for one of two unmet_requirements" do
171
+ before do
172
+ stub( @obj ).spacesuit?() { false }
173
+ stub( @obj ).fuel?() { false }
174
+ end
175
+ describe "when the arity of the proc is 1" do
176
+ before do
177
+ @msg = lambda { |trans| "I am a #{trans.class} and I fail it" }
178
+ @machine.requirement_messages[:spacesuit?] = @msg
179
+ end
180
+
181
+ it "should return an array with the requirement message and nil" do
182
+ t = @obj.state_fu.fly_spaceship(:moon)
183
+ t.unmet_requirements.length.should == 2
184
+ messages = t.unmet_requirement_messages
185
+ messages.should be_kind_of( Array )
186
+ messages.length.should == 2
187
+ messages.strings.length.should == 1
188
+ messages.strings.first.should be_kind_of( String )
189
+ messages.strings.first.should == "I am a StateFu::Transition and I fail it"
190
+ messages.symbols.first.should == :fuel?
191
+ end
192
+ end # arity 1
193
+
194
+ describe "when the arity of the proc is 0" do
195
+ before do
196
+ @msg = lambda { "No #{t.target.name} for you!" }
197
+ @machine.requirement_messages[:spacesuit?] = @msg
198
+ end
199
+
200
+ it "should return an array with the requirement message and nil" do
201
+ t = @obj.state_fu.fly_spaceship(:moon)
202
+ t.unmet_requirements.length.should == 2
203
+ messages = t.unmet_requirement_messages
204
+ messages.should be_kind_of( Array )
205
+ messages.length.should == 2
206
+ messages.strings.length.should == 1
207
+ messages.strings.first.should be_kind_of( String )
208
+ messages.strings.first.should == "No moon for you!"
209
+ messages.symbols.first.should == :fuel?
210
+ end
211
+ end # arity 1
212
+
213
+ end # 1 proc msg of 2
214
+ describe "when a symbol message is defined for one of two unmet_requirements" do
215
+ before do
216
+
217
+ @machine.requirement_messages[:spacesuit?] = :no_spacesuit_msg_method
218
+ Klass.class_eval do
219
+ attr_accessor :arg
220
+ def spacesuit?; false end
221
+ def fuel?; false end
222
+
223
+ def no_spacesuit_msg_method(t)
224
+ "You can't go to the #{t.target.name} without a spacesuit!"
225
+ end
226
+
227
+ end
228
+ end
229
+
230
+ describe "when there is no named proc on the machine matching the symbol" do
231
+
232
+ it "should call the method on @obj given transition.evaluate() with the method name" do
233
+ t = @obj.state_fu.fly_spaceship(:moon)
234
+ @obj.arg.should == nil
235
+ t.unmet_requirement_messages.should == ["You can't go to the moon without a spacesuit!", :fuel?]
236
+ end
237
+
238
+ it "should call t.evaluate_named_proc_or_method(:no_spacesuit_msg_method)" do
239
+ t = @obj.state_fu.fly_spaceship(:moon)
240
+ t.unmet_requirements.length.should == 2
241
+ stub( t ).evaluate( anything) { false }
242
+
243
+ mock( t ).evaluate(:no_spacesuit_msg_method){ ":)" }
244
+ messages = t.unmet_requirement_messages
245
+ messages.should include( ":)" )
246
+ end
247
+
248
+ it "should call the method on @obj with the name of the symbol, passing it a transition" do
249
+ t = @obj.state_fu.fly_spaceship(:moon)
250
+ t.unmet_requirements.length.should == 2
251
+ messages = t.unmet_requirement_messages
252
+ @obj.arg.should == nil
253
+ end
254
+
255
+ it "should return the result of the method execution as the message" do
256
+ t = @obj.state_fu.fly_spaceship( :moon )
257
+ t.unmet_requirements.length.should == 2
258
+ messages = t.unmet_requirement_messages
259
+ messages.length.should == 2
260
+ messages.strings.length.should == 1
261
+ #@obj.arg.should == t
262
+ messages.strings[0].should == "You can't go to the moon without a spacesuit!"
263
+ end
264
+ end # no named proc
265
+ end # symbol message
266
+ end # transition.unmet_requirement_messages
267
+ end
268
+
269
+
270
+