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,201 @@
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")
10
+ @machine = Klass.machine do
11
+ state :soviet_russia do
12
+ requires( :papers_in_order?,
13
+ :money_for_bribe?, :on => [:entry, :exit] )
14
+ end
15
+
16
+ state :america do
17
+ requires( :no_turban?,
18
+ :us_visa?,
19
+ :on => :entry )
20
+ requires( :no_arrest_warrant, :on => [:entry,:exit] )
21
+ end
22
+
23
+ state :moon do
24
+ requires :spacesuit?
25
+ end
26
+
27
+ event( :catch_plane,
28
+ :from => states.except(:moon),
29
+ :to => states.except(:moon) ) do
30
+ requires :plane_ticket?
31
+ end
32
+
33
+ event( :fly_spaceship,
34
+ :from => :ALL,
35
+ :to => :ALL ) do
36
+ requires :fuel?
37
+ end
38
+
39
+ end # machine
40
+ @obj = Klass.new()
41
+ stub( @obj ).papers_in_order? { true }
42
+ stub( @obj ).money_for_bribe? { true }
43
+ stub( @obj ).no_turban? { true }
44
+ stub( @obj ).no_arrest_warrant? { true }
45
+ stub( @obj ).spacesuit? { true }
46
+ stub( @obj ).plane_ticket? { true }
47
+ stub( @obj ).fuel? { true }
48
+ end # before
49
+
50
+ describe "transition.valid? / transition.requirements_met?" do
51
+ it "should be true if all requirements are met (return truth)" do
52
+ @obj.state_fu.next_states[:moon].entry_requirements.should == [:spacesuit?]
53
+ @obj.state_fu.evaluate_requirement(:spacesuit?).should == true
54
+ @obj.fly_spaceship?(:moon).should == true
55
+ @obj.fly_spaceship(:moon).requirements_met?.should == true
56
+ @obj.fly_spaceship(:moon).should be_valid
57
+ end
58
+
59
+ it "should be false if not all requirements are met" do
60
+ stub( @obj ).spacesuit?() { false }
61
+ @obj.state_fu.next_states[:moon].entry_requirements.should == [:spacesuit?]
62
+ @obj.state_fu.evaluate_requirement(:spacesuit?).should == false
63
+ @obj.fly_spaceship?(:moon).should == false
64
+ @obj.fly_spaceship(:moon).requirements_met?.should == false
65
+ @obj.fly_spaceship(:moon).should_not be_valid
66
+ end
67
+ end
68
+
69
+ describe "transition.unmet_requirements" do
70
+ it "should be empty when all requirements are met" do
71
+ @obj.state_fu.fly_spaceship(:moon).unmet_requirements.should == []
72
+ end
73
+
74
+ it "should contain a list of failing requirements" do
75
+ mock( @obj ).spacesuit?() { false }
76
+ mock( @obj ).fuel?() { false }
77
+ @obj.state_fu.fly_spaceship(:moon).unmet_requirements.should == [:spacesuit?, :fuel?]
78
+ end
79
+ end
80
+
81
+ describe "transition.unmet_requirement_messages" do
82
+ describe "when a string message is defined for one of two unmet_requirements" do
83
+ before do
84
+ stub( @obj ).spacesuit?() { false }
85
+ stub( @obj ).fuel?() { false }
86
+ @msg = "You got no spacesuit."
87
+ @machine.requirement_messages[:spacesuit?] = @msg
88
+ end
89
+
90
+ it "should return an array with the requirement message and nil" do
91
+ t = @obj.state_fu.fly_spaceship(:moon)
92
+ t.unmet_requirements.length.should == 2
93
+ messages = t.unmet_requirement_messages
94
+ messages.should be_kind_of( Array )
95
+ messages.length.should == 2
96
+ messages.compact.length.should == 1
97
+ messages.compact.first.should be_kind_of( String )
98
+ messages.compact.first.should == @msg
99
+ end
100
+ end
101
+
102
+ describe "when a proc message is defined for one of two unmet_requirements" do
103
+ before do
104
+ stub( @obj ).spacesuit?() { false }
105
+ stub( @obj ).fuel?() { false }
106
+ end
107
+ describe "when the arity of the proc is 1" do
108
+ before do
109
+ @msg = lambda { |trans| "I am a #{trans.class} and I fail it" }
110
+ @machine.requirement_messages[:spacesuit?] = @msg
111
+ end
112
+
113
+ it "should return an array with the requirement message and nil" do
114
+ t = @obj.state_fu.fly_spaceship(:moon)
115
+ t.unmet_requirements.length.should == 2
116
+ messages = t.unmet_requirement_messages
117
+ messages.should be_kind_of( Array )
118
+ messages.length.should == 2
119
+ messages.compact.length.should == 1
120
+ messages.compact.first.should be_kind_of( String )
121
+ messages.compact.first.should == "I am a StateFu::Transition and I fail it"
122
+ end
123
+ end # arity 1
124
+
125
+ describe "when the arity of the proc is 0" do
126
+ before do
127
+ @msg = lambda { "I am a #{self.class} and I fail it" }
128
+ @machine.requirement_messages[:spacesuit?] = @msg
129
+ end
130
+
131
+ it "should return an array with the requirement message and nil" do
132
+ t = @obj.state_fu.fly_spaceship(:moon)
133
+ t.unmet_requirements.length.should == 2
134
+ messages = t.unmet_requirement_messages
135
+ messages.should be_kind_of( Array )
136
+ messages.length.should == 2
137
+ messages.compact.length.should == 1
138
+ messages.compact.first.should be_kind_of( String )
139
+ messages.compact.first.should == "I am a StateFu::Transition and I fail it"
140
+ end
141
+ end # arity 1
142
+
143
+ end # 1 proc msg of 2
144
+ describe "when a symbol message is defined for one of two unmet_requirements" do
145
+ before do
146
+ stub( @obj ).spacesuit?() { false }
147
+ stub( @obj ).fuel?() { false }
148
+ @machine.requirement_messages[:spacesuit?] = :no_spacesuit_msg_method
149
+ Klass.class_eval do
150
+ attr_accessor :arg
151
+
152
+ def no_spacesuit_msg_method( t )
153
+ self.arg = t
154
+ raise ArgumentError unless t.is_a?( StateFu::Transition )
155
+ "You can't go to the #{t.target.name} without a spacesuit!"
156
+ end
157
+ end
158
+ end
159
+
160
+ describe "when there is no named proc on the machine matching the symbol" do
161
+
162
+ it "should call the method on @obj given transition.evaluate_named_proc_or_method() with the method name" do
163
+ @obj.method( :no_spacesuit_msg_method ).arity.should == 1
164
+ t = @obj.state_fu.fly_spaceship(:moon)
165
+ x = t.evaluate_named_proc_or_method(:no_spacesuit_msg_method)
166
+ @obj.arg.should == t
167
+ x.should =~ /You can't go to the moon/
168
+ end
169
+
170
+ it "should call t.evaluate_named_proc_or_method(:no_spacesuit_msg_method)" do
171
+ t = @obj.state_fu.fly_spaceship(:moon)
172
+ t.unmet_requirements.length.should == 2
173
+ mock( t ).evaluate_named_proc_or_method(:no_spacesuit_msg_method) { :my_string }
174
+ messages = t.unmet_requirement_messages
175
+ messages.should include(:my_string )
176
+ end
177
+
178
+ it "should call the method on @obj with the name of the symbol, passing it a transition" do
179
+ t = @obj.state_fu.fly_spaceship(:moon)
180
+ t.unmet_requirements.length.should == 2
181
+ messages = t.unmet_requirement_messages
182
+ @obj.arg.should == t
183
+ end
184
+
185
+ it "should return the result of the method execution as the message" do
186
+ t = @obj.state_fu.fly_spaceship(:moon)
187
+ t.unmet_requirements.length.should == 2
188
+ messages = t.unmet_requirement_messages
189
+ messages.length.should == 2
190
+ messages.compact.length.should == 1
191
+ @obj.arg.should == t
192
+ messages.compact[0].should == "You can't go to the moon without a spacesuit!"
193
+ end
194
+ end # no named proc
195
+ end # symbol message
196
+ end # transition.unmet_requirement_messages
197
+
198
+ end
199
+
200
+
201
+
@@ -0,0 +1,31 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ class Moo
4
+ def arity_1( a )
5
+ raise "!"
6
+ end
7
+
8
+ def arity_0( )
9
+ raise "!"
10
+ end
11
+ end
12
+
13
+ describe "sanity check: rr / arity" do
14
+ it "should have the expected arity for standard methods" do
15
+ m = Moo.new()
16
+ m.method(:arity_1).arity.should == 1
17
+ m.method(:arity_0).arity.should == 0
18
+ end
19
+
20
+ it "should have the expected arity when methods are mocked" do
21
+ m = Moo.new()
22
+ a1 = Object.new
23
+ a0 = Object.new
24
+ stub( a1 ).arity() { 1 }
25
+ stub( a0 ).arity() { 0 }
26
+ stub( m ).method(:arity_1) { a1 }
27
+ stub( m ).method(:arity_0) { a0 }
28
+ m.method(:arity_1).arity.should == 1
29
+ m.method(:arity_0).arity.should == 0
30
+ end
31
+ end
@@ -0,0 +1,177 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ ##
4
+ ##
5
+ ##
6
+
7
+ describe "Adding states to a Machine" do
8
+
9
+ include MySpecHelper
10
+
11
+ before(:each) do
12
+ make_pristine_class 'Klass'
13
+ @k = Klass.new()
14
+ end
15
+
16
+ it "should allow me to call machine() { state(:egg) }" do
17
+ lambda {Klass.machine(){ state :egg } }.should_not raise_error()
18
+ end
19
+
20
+ describe "having called machine() { state(:egg) }" do
21
+
22
+ before(:each) do
23
+ Klass.machine(){ state :egg }
24
+ end
25
+
26
+ it "should return [:egg] given machine.state_names" do
27
+ Klass.machine.should respond_to(:state_names)
28
+ Klass.machine.state_names.should == [:egg]
29
+ end
30
+
31
+ it "should return [<StateFu::State @name=:egg>] given machine.states" do
32
+ Klass.machine.should respond_to(:states)
33
+ Klass.machine.states.length.should == 1
34
+ Klass.machine.states.first.should be_kind_of( StateFu::State )
35
+ Klass.machine.states.first.name.should == :egg
36
+ end
37
+
38
+ it "should return :egg given machine.states.first.name" do
39
+ Klass.machine.should respond_to(:states)
40
+ Klass.machine.states.length.should == 1
41
+ Klass.machine.states.first.should respond_to(:name)
42
+ Klass.machine.states.first.name.should == :egg
43
+ end
44
+
45
+ it "should return a <StateFu::State @name=:egg> given machine.states[:egg]" do
46
+ Klass.machine.should respond_to(:states)
47
+ result = Klass.machine.states[:egg]
48
+ result.should_not be_nil
49
+ result.should be_kind_of( StateFu::State )
50
+ result.name.should == :egg
51
+ end
52
+
53
+
54
+ it "should allow me to call machine(){ state(:chick) }" do
55
+ lambda {Klass.machine(){ state :chick } }.should_not raise_error()
56
+ end
57
+
58
+ describe "having called machine() { state(:chick) }" do
59
+ before do
60
+ Klass.machine() { state :chick }
61
+ end
62
+
63
+ it "should return [:egg] given machine.state_names" do
64
+ Klass.machine.should respond_to(:state_names)
65
+ Klass.machine.state_names.should == [:egg, :chick]
66
+ end
67
+
68
+ it "should return a <StateFu::State @name=:chick> given machine.states[:egg]" do
69
+ Klass.machine.should respond_to(:states)
70
+ result = Klass.machine.states[:chick]
71
+ result.should_not be_nil
72
+ result.should be_kind_of( StateFu::State )
73
+ result.name.should == :chick
74
+ end
75
+
76
+ end
77
+
78
+ describe "calling machine() { state(:bird) {|s| .. } }" do
79
+
80
+ it "should yield the state to the block as |s|" do
81
+ state = nil
82
+ Klass.machine() do
83
+ state(:bird) do |s|
84
+ state = s
85
+ end
86
+ end
87
+ state.should be_kind_of( StateFu::State )
88
+ state.name.should == :bird
89
+ end
90
+
91
+ end
92
+
93
+ describe "calling machine() { state(:bird) { ... } }" do
94
+
95
+ it "should instance_eval the block as a StateFu::Lathe" do
96
+ lathe = nil
97
+ Klass.machine() do
98
+ state(:bird) do
99
+ lathe = self
100
+ end
101
+ end
102
+ lathe.should be_kind_of(StateFu::Lathe)
103
+ lathe.sprocket.should be_kind_of(StateFu::State)
104
+ lathe.sprocket.name.should == :bird
105
+ end
106
+
107
+ end
108
+
109
+ describe "calling state(:bird) consecutive times" do
110
+
111
+ it "should yield the same state each time" do
112
+ Klass.machine() { state :bird }
113
+ bird_1 = Klass.machine.states[:bird]
114
+ Klass.machine() { state :bird }
115
+ bird_2 = Klass.machine.states[:bird]
116
+ bird_1.should == bird_2
117
+ end
118
+
119
+ end
120
+ end
121
+
122
+ describe "calling machine() { states(:egg, :chick, :bird, :poultry => true) }" do
123
+
124
+ it "should create 3 states" do
125
+ Klass.machine().should be_empty
126
+ Klass.machine() { states(:egg, :chick, :bird, :poultry => true) }
127
+ Klass.machine().state_names().should == [:egg, :chick, :bird]
128
+ Klass.machine().states.length.should == 3
129
+ Klass.machine().states.map(&:name).should == [:egg, :chick, :bird]
130
+ Klass.machine().states().each do |s|
131
+ s.options[:poultry].should be_true
132
+ s.should be_kind_of(StateFu::State)
133
+ end
134
+
135
+ describe "merging options" do
136
+ it "should merge options when states are mentioned more than once" do
137
+ StateFu::FuSpace.reset!
138
+ Klass.machine() { states(:egg, :chick, :bird, :poultry => true) }
139
+ machine = Klass.machine
140
+ machine.states.length.should == 3
141
+
142
+ # make sure they're the same states
143
+ states_1 = machine.states
144
+ Klass.machine(){ states( :egg, :chick, :bird, :covering => 'feathers')}
145
+ states_1.should == machine.states
146
+
147
+ # ensure options were merged
148
+ machine.states().each do |s|
149
+ s.options[:poultry].should be_true
150
+ s.options[:covering].should == 'feathers'
151
+ s.should be_kind_of(StateFu::State)
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ describe "adding events inside a state block" do
159
+ before do
160
+ @lambda = lambda{ Klass.machine(){ state(:egg){ event(:hatch, :to => :chick) }}}
161
+ end
162
+
163
+ it "should not throw an error" do
164
+ @lambda.should_not raise_error
165
+ end
166
+
167
+ describe "Klass.machine(){ state(:egg){ event(:hatch, :to => :chick) }}}" do
168
+ before() do
169
+ Klass.machine(){ state(:egg){ event(:hatch, :to => :chick) }}
170
+ end
171
+ it "should add an event :hatch to the machine" do
172
+ end
173
+ end
174
+ end
175
+
176
+ end
177
+