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
data/spec/spec.opts ADDED
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
7
+ --backtrace
@@ -0,0 +1,145 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe StateFu::Binding do
4
+ include MySpecHelper
5
+
6
+ before do
7
+ reset!
8
+ make_pristine_class('Klass')
9
+ Klass.machine(){}
10
+ @obj = Klass.new()
11
+ end
12
+
13
+ describe "constructor" do
14
+ before do
15
+ mock( StateFu::FuSpace ).field_names() do
16
+ {
17
+ Klass => { :example => :example_field }
18
+ }
19
+ end
20
+ end
21
+
22
+ it "should create a new Binding given valid arguments" do
23
+ b = StateFu::Binding.new( Klass.machine, @obj, :example )
24
+ b.should be_kind_of( StateFu::Binding )
25
+ b.object.should == @obj
26
+ b.machine.should == Klass.machine
27
+ b.method_name.should == :example
28
+ end
29
+
30
+ it "should add any options supplied to the binding" do
31
+ b = StateFu::Binding.new( Klass.machine, @obj, :example,
32
+ :colour => :red,
33
+ :style => [:robust, :fruity] )
34
+ b.options.should == { :colour => :red, :style => [:robust, :fruity] }
35
+ end
36
+
37
+ describe "persister initialization" do
38
+ before do
39
+ @p = Object.new
40
+ class << @p
41
+ attr_accessor :field_name
42
+ end
43
+ @p.field_name
44
+ end
45
+
46
+ describe "when StateFu::Persistence.active_record_column? is true" do
47
+ before do
48
+ mock( StateFu::Persistence ).active_record_column?(Klass, :example_field).times(2) { true }
49
+ mock( Klass ).before_save( :state_fu!) { }
50
+ end
51
+ it "should get an ActiveRecord persister" do
52
+ mock( StateFu::Persistence::ActiveRecord ).new( anything, :example_field ) { @p }
53
+ b = StateFu::Binding.new( Klass.machine, @obj, :example )
54
+ b.persister.should == @p
55
+ end
56
+ end
57
+
58
+ describe "when StateFu::Persistence.active_record_column? is false" do
59
+ before do
60
+ mock( StateFu::Persistence ).active_record_column?(Klass, :example_field).times(2) { false }
61
+ end
62
+ it "should get an Attribute persister" do
63
+ mock( StateFu::Persistence::Attribute ).new( anything, :example_field ) { @p }
64
+ b = StateFu::Binding.new( Klass.machine, @obj, :example )
65
+ b.persister.should == @p
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "initialization via @obj.state_fu()" do
72
+ it "should create a new StateFu::Binding with default method-name & field_name" do
73
+ b = @obj.state_fu()
74
+ b.should be_kind_of( StateFu::Binding )
75
+ b.machine.should == Klass.machine
76
+ b.object.should == @obj
77
+ b.method_name.should == :state_fu
78
+ b.field_name.should == :state_fu_field
79
+ end
80
+ end
81
+
82
+ describe "a binding for the default machine with two states and an event" do
83
+ before do
84
+ reset!
85
+ make_pristine_class('Klass')
86
+ Klass.machine do
87
+ state :new do
88
+ event :age, :to => :old
89
+ end
90
+ state :old
91
+ end
92
+ @machine = Klass.machine()
93
+ @object = Klass.new()
94
+ @binding = @object.state_fu()
95
+ end
96
+
97
+ describe ".state() / initial state" do
98
+ it "should default to machine.initial_state when no initial_state is explicitly defined" do
99
+ @machine.initial_state.name.should == :new
100
+ @binding.current_state.should == @machine.initial_state
101
+ end
102
+
103
+ it "should default to the machine's initial_state if one is set" do
104
+ @machine.initial_state = :fetus
105
+ @machine.initial_state.name.should == :fetus
106
+ obj = Klass.new
107
+ obj.state_fu.current_state.should == @machine.initial_state
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ describe "Instance methods" do
114
+ before do
115
+ end
116
+ describe "fireable?" do
117
+ describe "when called with arguments which would return a valid transition from .transition()" do
118
+ it "should return true"
119
+ end
120
+
121
+ describe "when called with arguments which would raise an InvalidTransition from .transition()" do
122
+ before do
123
+ reset!
124
+ make_pristine_class("Klass")
125
+ @machine = Klass.machine do
126
+ state :snoo
127
+ state :wizz do
128
+ event :ping, :to => :pong
129
+ end
130
+ end
131
+ @obj = Klass.new
132
+ end
133
+
134
+ it "should return nil" do
135
+ @obj.state_fu.name.should == :snoo
136
+ lambda { @obj.state_fu.transition(:ping) }.should raise_error( StateFu::InvalidTransition )
137
+ lambda { @obj.state_fu.fireable?(:ping) }.should_not raise_error( StateFu::InvalidTransition )
138
+ @obj.state_fu.fireable?(:ping).should == nil
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,232 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ ##
4
+ ##
5
+ ##
6
+
7
+ describe StateFu::Event do
8
+ include MySpecHelper
9
+ before do
10
+ @machine = Object.new
11
+ end
12
+
13
+ describe "Instance methods" do
14
+ before do
15
+ @name = :germinate
16
+ @options = {:speed => :slow}
17
+ @event = StateFu::Event.new( @machine, @name, @options )
18
+ @state_a = StateFu::State.new( @machine,:a )
19
+ @state_b = StateFu::State.new( @machine,:b )
20
+ @initial = Object.new
21
+ @final = Object.new
22
+ @start = Object.new
23
+ @end = Object.new
24
+ end
25
+
26
+
27
+ describe "Instance methods" do
28
+ describe "setting origin / target" do
29
+
30
+ describe "target" do
31
+ it "should be nil if targets is nil" do
32
+ stub( @event ).targets() { nil }
33
+ @event.target.should == nil
34
+ end
35
+
36
+ it "should be nil if targets has more than one state" do
37
+ stub( @event ).targets() { [@state_a, @state_b] }
38
+ @event.target.should == nil
39
+ end
40
+
41
+ it "should be the sole state if targets is set and there is only one" do
42
+ stub( @event ).targets() { [@state_a] }
43
+ @event.target.should == @state_a
44
+ end
45
+ end
46
+
47
+ describe 'origins=' do
48
+ it "should call get_states_list_by_name with its argument" do
49
+ mock( @machine ).find_or_create_states_by_name( :initial ) { }
50
+ @event.origins= :initial
51
+ end
52
+
53
+ it "should set @origin to the result" do
54
+ mock( @machine ).find_or_create_states_by_name( :initial ) { :result }
55
+ @event.origins= :initial
56
+ @event.origins.should == :result
57
+ end
58
+
59
+ end
60
+
61
+ describe 'targets=' do
62
+ it "should call get_states_list_by_name with its argument" do
63
+ mock( @machine ).find_or_create_states_by_name( :initial ) { }
64
+ @event.targets= :initial
65
+ end
66
+
67
+ it "should set @target to the result" do
68
+ mock( @machine ).find_or_create_states_by_name( :initial ) { :result }
69
+ @event.targets= :initial
70
+ @event.targets.should == :result
71
+ end
72
+ end
73
+
74
+ describe "lathe" do
75
+ before do
76
+ @lathe = @event.lathe()
77
+ end
78
+
79
+ it "should return a StateFu::Lathe" do
80
+ @lathe.should be_kind_of( StateFu::Lathe )
81
+ end
82
+
83
+ it "should have the event's machine" do
84
+ @lathe.machine.should == @event.machine()
85
+ end
86
+
87
+ it "should have the event as the sprocket" do
88
+ @lathe.sprocket.should == @event
89
+ end
90
+
91
+ end
92
+
93
+ describe '.from()' do
94
+ describe "given @event.from :initial, :to => :final" do
95
+ describe "setting attributes" do
96
+ before do
97
+ stub( @machine ).find_or_create_states_by_name( anything ) { |*a| raise(a.inspect) }
98
+ stub( @machine ).find_or_create_states_by_name( :initial ) { [@initial] }
99
+ stub( @machine ).find_or_create_states_by_name( :final ) { [@final] }
100
+ end
101
+
102
+ it "should call @machine.find_or_create_states_by_name() with :initial and :final" do
103
+ @event.from :initial, :to => :final
104
+ end
105
+
106
+ it "should set @event.origin to the returned array of origin states" do
107
+ @event.from :initial, :to => :final
108
+ @event.origins.should == [@initial]
109
+ end
110
+
111
+ it "should set @event.target to the returned array of target states" do
112
+ @event.from :initial, :to => :final
113
+ @event.targets.should == [@final]
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "given @event.from <Array>, :to => <Array>" do
119
+ it "should call @machine.find_or_create_states_by_name() with both arrays" do
120
+ stub( @machine ).find_or_create_states_by_name(:initial, :start) do
121
+ [@initial, @start]
122
+ end
123
+ stub( @machine ).find_or_create_states_by_name(:final, :end) do
124
+ [@final, @end]
125
+ end
126
+
127
+ @event.from( [:initial, :start], :to => [:final, :end] )
128
+ end
129
+ end
130
+
131
+ describe "given @event.from :ALL, :to => :ALL" do
132
+ it "should set origins and targets to @machine.states" do
133
+ stub( @machine ).states() { [:all, :of, :them ] }
134
+ stub( @machine ).find_or_create_states_by_name(anything) do |x|
135
+ x
136
+ end
137
+ @event.from( :ALL, :to => :ALL )
138
+ @event.origins.should == [:all, :of, :them ]
139
+ @event.targets.should == [:all, :of, :them ]
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ describe '.to()' do
146
+ describe "given :final" do
147
+ it "should set @event.target to machine.find_or_create_states_by_name( :final )" do
148
+ mock( @machine ).find_or_create_states_by_name(:final) { [@final] }
149
+ @event.to :final
150
+ @event.targets.should == [@final]
151
+ end
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ describe 'origin_names' do
158
+ it "should return an array of state names in origin when origin is not nil" do
159
+ mock( @machine ).find_or_create_states_by_name(:initial) { [@initial] }
160
+ mock( @machine ).find_or_create_states_by_name(:final) { [@final] }
161
+ @event.from :initial, :to => :final
162
+ @event.origin.should == @initial
163
+ mock( @initial ).to_sym().times(any_times) { :initial }
164
+ @event.origin_names.should == [:initial]
165
+ end
166
+
167
+ it "should return nil when origin is nil" do
168
+ mock( @event ).origins().times(any_times) { nil }
169
+ @event.origin_names.should == nil
170
+ end
171
+
172
+ end
173
+
174
+ describe 'target_names' do
175
+ it "should return an array of state names in target when target is not nil" do
176
+ mock( @event ).targets.times( any_times ) { [@final] }
177
+ mock( @final ).to_sym { :final }
178
+ @event.target_names.should == [:final]
179
+ end
180
+
181
+ it "should return nil when target is nil" do
182
+ mock( @event ).targets().times(any_times) { nil }
183
+ @event.target_names.should == nil
184
+ end
185
+ end
186
+
187
+ describe 'to?' do
188
+ it "should return true given a symbol which is the name of a state in @target" do
189
+ mock( @event ).targets.times(any_times) { [StateFu::State.new(@machine,:a)] }
190
+ @event.to?( :a ).should == true
191
+ end
192
+
193
+ it "should return false given a symbol which is not the name of a state in @target" do
194
+ mock( @event ).targets.times(any_times) { [StateFu::State.new(@machine,:a)] }
195
+ @event.to?( :b ).should == false
196
+ end
197
+ end
198
+
199
+ describe 'from?' do
200
+ it "should return true given a symbol which is the name of a state in @origin" do
201
+ mock( @event ).origins.times(any_times) { [StateFu::State.new(@machine,:a)] }
202
+ @event.from?( :a ).should == true
203
+ end
204
+
205
+ it "should return false given a symbol which is not the name of a state in @origin" do
206
+ mock( @event ).origins().times(any_times) { [StateFu::State.new(@machine,:a)] }
207
+ @event.from?( :b ).should == false
208
+ end
209
+ end
210
+
211
+ describe 'complete?' do
212
+ it "should be false if either origin / target are nil" do
213
+ @event.complete?.should == false
214
+ end
215
+
216
+ it "should be true when origin / target are both not nil" do
217
+ mock( @event ).origins { [:a] }
218
+ mock( @event ).targets { [:b] }
219
+ @event.complete?.should == true
220
+ end
221
+
222
+ it "should be false when either origin / target are nil" do
223
+ mock( @event ).origins { [:a] }
224
+ mock( @event ).targets { nil }
225
+ @event.complete?.should == false
226
+ end
227
+
228
+ end
229
+
230
+ end # describe instance methods
231
+ end # describe StateFu::Event
232
+ end
@@ -0,0 +1,75 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe StateFu::RequirementError do
4
+
5
+ describe "constructor" do
6
+ before do
7
+ @transition = Object.new()
8
+ end
9
+
10
+ end
11
+ end
12
+
13
+ describe StateFu::TransitionHalted do
14
+
15
+ describe "constructor" do
16
+ before do
17
+ @transition = Object.new()
18
+ end
19
+
20
+ it "should create a TransitionHalted given a transition" do
21
+ e = StateFu::TransitionHalted.new( @transition )
22
+ e.should be_kind_of( StateFu::TransitionHalted )
23
+ end
24
+
25
+ it "should allow a custom message" do
26
+ msg = 'helo'
27
+ e = StateFu::TransitionHalted.new( @transition, msg )
28
+ e.should be_kind_of( StateFu::TransitionHalted )
29
+ e.message.should == msg
30
+ end
31
+
32
+ it "should allow a message to be omitted" do
33
+ e = StateFu::TransitionHalted.new( @transition )
34
+ e.should be_kind_of( StateFu::TransitionHalted )
35
+ e.message.should == StateFu::TransitionHalted::DEFAULT_MESSAGE
36
+ end
37
+
38
+ it "should allow access to the transition" do
39
+ e = StateFu::TransitionHalted.new( @transition )
40
+ e.transition.should == @transition
41
+ end
42
+ end
43
+ end
44
+
45
+ describe StateFu::InvalidTransition do
46
+ before do
47
+ @binding = Object.new
48
+ @origin = Object.new
49
+ @event = Object.new
50
+ @target = Object.new
51
+ end
52
+
53
+ describe "constructor" do
54
+ it "should create an InvalidTransition given a binding, event, origin & target" do
55
+ e = StateFu::InvalidTransition.new( @binding, @event, @origin, @target )
56
+ e.should be_kind_of( StateFu::InvalidTransition )
57
+ e.message.should == StateFu::InvalidTransition::DEFAULT_MESSAGE
58
+ end
59
+
60
+ it "should allow a custom message" do
61
+ msg = 'helo'
62
+ e = StateFu::InvalidTransition.new( @binding, @event, @origin, @target, msg )
63
+ e.should be_kind_of( StateFu::InvalidTransition )
64
+ e.message.should == msg
65
+ end
66
+
67
+ it "should allow access to the binding, event, origin, and target" do
68
+ e = StateFu::InvalidTransition.new( @binding, @event, @origin, @target )
69
+ e.binding.should == @binding
70
+ e.event.should == @event
71
+ e.origin.should == @origin
72
+ e.target.should == @target
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,95 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ StateFu::FuSpace.reset!
4
+
5
+ ##
6
+ ##
7
+ ##
8
+ describe StateFu::FuSpace do
9
+ include MySpecHelper
10
+
11
+ before(:each) do
12
+ reset!
13
+ make_pristine_class 'Klass'
14
+ @k = Klass.new()
15
+ end
16
+
17
+ describe "Before any Machine is defined" do
18
+ it "should return {} given StateFu::FuSpace.class_machines()" do
19
+ StateFu::FuSpace.should respond_to(:class_machines)
20
+ StateFu::FuSpace.class_machines.should == {}
21
+ end
22
+ end
23
+
24
+ describe "Having called Klass.machine() with an empty block:" do
25
+ before(:each) do
26
+ Klass.machine do
27
+ end
28
+ StateFu::DEFAULT_MACHINE.should == :state_fu
29
+ end
30
+
31
+ it "should return { Klass => { ... } } given StateFu::FuSpace.class_machines()" do
32
+ StateFu::FuSpace.should respond_to(:class_machines)
33
+ machines = StateFu::FuSpace.class_machines()
34
+ machines.keys.should == [Klass]
35
+ machines.values.first.should be_kind_of( Hash )
36
+ end
37
+
38
+ it "should return { :state_fu => <StateFu::Machine> } given StateFu::FuSpace.class_machines[Klass]" do
39
+ StateFu::FuSpace.should respond_to(:class_machines)
40
+ machines = StateFu::FuSpace.class_machines[Klass]
41
+ machines.should be_kind_of(Hash)
42
+ machines.should_not be_empty
43
+ machines.length.should == 1
44
+ machines.keys.should == [:state_fu]
45
+ machines.values.first.should be_kind_of( StateFu::Machine )
46
+ end
47
+
48
+ it "should return { Klass => { ... } } given StateFu::FuSpace.field_names()" do
49
+ StateFu::FuSpace.should respond_to(:field_names)
50
+ fields = StateFu::FuSpace.field_names()
51
+ fields.keys.should == [Klass]
52
+ fields.values.first.should be_kind_of( Hash )
53
+ end
54
+
55
+ it "should return { :state_fu => :state_fu_state } given StateFu::FuSpace.field_names[Klass]" do
56
+ StateFu::FuSpace.should respond_to(:field_names)
57
+ fields = StateFu::FuSpace.field_names[Klass]
58
+ fields.should be_kind_of(Hash)
59
+ fields.should_not be_empty
60
+ fields.length.should == 1
61
+ fields.keys.should == [:state_fu]
62
+ fields.values.should == [:state_fu_field]
63
+ end
64
+
65
+ describe "Having called Klass.machine(:two) with an empty block:" do
66
+ before(:each) do
67
+ # Klass.machine.should_not be_nil
68
+ Klass.machine(:two) do
69
+ end
70
+ end
71
+
72
+ it "should return { :state_fu => <StateFu::Machine>, :two => <StateFu::Machine> } given StateFu::FuSpace.class_machines()" do
73
+ StateFu::FuSpace.should respond_to(:class_machines)
74
+ machines = StateFu::FuSpace.class_machines[Klass]
75
+ machines.should be_kind_of(Hash)
76
+ machines.should_not be_empty
77
+ machines.length.should == 2
78
+ machines.keys.sort.should == [:state_fu, :two]
79
+ machines.values.each { |v| v.should be_kind_of( StateFu::Machine ) }
80
+ end
81
+
82
+ describe "Having called StateFu::FuSpace.reset!" do
83
+ before(:each) do
84
+ StateFu::FuSpace.reset!
85
+ end
86
+ it "should return {} given StateFu::FuSpace.class_machines()" do
87
+ StateFu::FuSpace.should respond_to(:class_machines)
88
+ StateFu::FuSpace.class_machines.should == {}
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+