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,192 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe StateFu::Binding do
4
+ include MySpecHelper
5
+
6
+ describe "instance methods" do
7
+ end
8
+
9
+ #
10
+ # class methods
11
+ #
12
+
13
+ describe "class methods" do
14
+ end
15
+
16
+
17
+ #
18
+ #
19
+ #
20
+
21
+
22
+
23
+ #
24
+ #
25
+ #
26
+
27
+ before do
28
+ reset!
29
+ make_pristine_class('Klass')
30
+ Klass.state_fu_machine(){}
31
+ @obj = Klass.new()
32
+ end
33
+
34
+ describe "constructor" do
35
+ before do
36
+ mock(Klass).state_fu_field_names.at_most(1) do
37
+ { :example => :example_field }
38
+ end
39
+ end
40
+
41
+ it "should create a new Binding given valid arguments" do
42
+ b = StateFu::Binding.new( Klass.state_fu_machine, @obj, :example )
43
+ b.should be_kind_of( StateFu::Binding )
44
+ b.object.should == @obj
45
+ b.machine.should == Klass.state_fu_machine
46
+ b.method_name.should == :example
47
+ end
48
+
49
+ it "should add any options supplied to the binding" do
50
+ b = StateFu::Binding.new( Klass.state_fu_machine, @obj, :example,
51
+ :colour => :red,
52
+ :style => [:robust, :fruity] )
53
+ b.options.should == { :colour => :red, :style => [:robust, :fruity] }
54
+ end
55
+
56
+ describe "persister initialization" do
57
+ before do
58
+ @p = Object.new
59
+ class << @p
60
+ attr_accessor :field_name
61
+ end
62
+ @p.field_name
63
+ end
64
+
65
+ describe "when StateFu::Persistence.active_record_column? is true" do
66
+ before do
67
+ mock( StateFu::Persistence ).active_record_column?(Klass, :example_field).times(1) { true }
68
+ mock( Klass ).before_create( :state_fu!) { }
69
+ end
70
+ it "should get an ActiveRecord persister" do
71
+ mock( StateFu::Persistence::ActiveRecord ).new( anything, :example_field ) { @p }
72
+ b = StateFu::Binding.new( Klass.state_fu_machine, @obj, :example )
73
+ b.persister.should == @p
74
+ end
75
+ end
76
+
77
+ describe "when StateFu::Persistence.active_record_column? is false" do
78
+ before do
79
+ mock( StateFu::Persistence ).active_record_column?(Klass, :example_field) { false }
80
+ end
81
+ it "should get an Attribute persister" do
82
+ mock( StateFu::Persistence::Attribute ).new( anything, :example_field ) { @p }
83
+ b = StateFu::Binding.new( Klass.state_fu_machine, @obj, :example )
84
+ b.persister.should == @p
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "initialization via @obj.state_fu()" do
91
+ it "should create a new StateFu::Binding with default method-name & field_name" do
92
+ b = @obj.state_fu()
93
+ b.should be_kind_of( StateFu::Binding )
94
+ b.machine.should == Klass.state_fu_machine
95
+ b.object.should == @obj
96
+ b.method_name.should == StateFu::DEFAULT
97
+ b.field_name.to_sym.should == StateFu::DEFAULT_FIELD
98
+ end
99
+ end
100
+
101
+ describe "a binding for the default machine with two states and an event" do
102
+ before do
103
+ reset!
104
+ make_pristine_class('Klass')
105
+ Klass.state_fu_machine do
106
+ state :new do
107
+ event :age, :to => :old
108
+ end
109
+ state :old
110
+ end
111
+ @machine = Klass.state_fu_machine()
112
+ @object = Klass.new()
113
+ @binding = @object.state_fu()
114
+ end
115
+
116
+ describe "==" do
117
+ it "should be == :new" do
118
+ @binding.should == :new
119
+ end
120
+ end
121
+
122
+ describe ".state and .initial_state" do
123
+ it "should default to machine.initial_state when no initial_state is explicitly defined" do
124
+ @machine.initial_state.name.should == :new
125
+ @binding.current_state.should == @machine.initial_state
126
+ end
127
+
128
+ it "should default to the machine's initial_state if one is set" do
129
+ @machine.initial_state = :fetus
130
+ @machine.initial_state.name.should == :fetus
131
+ obj = Klass.new
132
+ obj.state_fu.current_state.should == @machine.initial_state
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ describe "Instance methods" do
139
+ before do
140
+ end
141
+ describe "can_transition?" do
142
+ before do
143
+ reset!
144
+ make_pristine_class("Klass")
145
+ Klass.class_eval do
146
+ def tissue?(*args); "o_O"; end
147
+ end
148
+ @machine = Klass.state_fu_machine do
149
+ state :snoo do
150
+ event :fire, :to => :wizz do
151
+ requires :tissue?
152
+ end
153
+ end
154
+ state :wizz do
155
+ event :not_fireable, :to => :pong
156
+ end
157
+ end
158
+ @obj = Klass.new
159
+ end
160
+
161
+ describe "when called with arguments which would return a valid transition from .transition()" do
162
+ it "should return true" do
163
+ @obj.state_fu.can_transition?(:fire).should == true
164
+ end
165
+ end
166
+
167
+ describe "when called with arguments which would raise an IllegalTransition from .transition()" do
168
+ it "should return nil" do
169
+ @obj.state_fu.name.should == :snoo
170
+ lambda { @obj.state_fu.can_transition?(:not_fire) }.should_not raise_error( StateFu::IllegalTransition )
171
+ @obj.state_fu.can_transition?(:not_fire).should == nil
172
+ end
173
+ end
174
+
175
+ describe "when called with additional arguments after the destination event/state" do
176
+
177
+ # This would make very little sense to someone trying to understand how to use the library.
178
+ it "should pass the arguments to any requirements to determine transition availability" do
179
+ pending
180
+ t = nil
181
+ mock(@obj).tissue?(anything) do
182
+ # current_transition.should be_kind_of(StateFu::Transition)
183
+ t << current_transition
184
+ end #{|tr| tr.args.should == [:a,:b] }
185
+ @obj.state_fu.can_fire?(:a, :b)
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+ end
@@ -0,0 +1,214 @@
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
+ stub(@machine).tools() { [].extend( StateFu::ToolArray ) }
12
+ end
13
+
14
+ describe "Instance methods" do
15
+ before do
16
+ @name = :germinate
17
+ @options = {:speed => :slow}
18
+ @event = StateFu::Event.new( @machine, @name, @options )
19
+ @state_a = StateFu::State.new( @machine,:a )
20
+ @state_b = StateFu::State.new( @machine,:b )
21
+ @initial = Object.new
22
+ @final = Object.new
23
+ @start = Object.new
24
+ @end = Object.new
25
+ end
26
+
27
+
28
+ describe "Instance methods" do
29
+ describe "setting origin / target" do
30
+
31
+ describe "target" do
32
+ it "should be nil if targets is nil" do
33
+ stub( @event ).targets() { nil }
34
+ @event.target.should == nil
35
+ end
36
+
37
+ it "should be nil if targets has more than one state" do
38
+ stub( @event ).targets() { [@state_a, @state_b] }
39
+ @event.target.should == nil
40
+ end
41
+
42
+ it "should be the sole state if targets is set and there is only one" do
43
+ stub( @event ).targets() { [@state_a] }
44
+ @event.target.should == @state_a
45
+ end
46
+ end
47
+
48
+ describe 'origins=' do
49
+ it "should call get_states_list_by_name with its argument" do
50
+ mock( @machine ).find_or_create_states_by_name( :initial ) { }
51
+ @event.origins= :initial
52
+ end
53
+
54
+ it "should set @origin to the result" do
55
+ mock( @machine ).find_or_create_states_by_name( :initial ) { [:result] }
56
+ @event.origins= :initial
57
+ @event.origins.should == [:result]
58
+ end
59
+
60
+ end
61
+
62
+ describe 'targets=' do
63
+ it "should call get_states_list_by_name with its argument" do
64
+ mock( @machine ).find_or_create_states_by_name( :initial ) { [] }
65
+ @event.targets= :initial
66
+ end
67
+
68
+ it "should set @target to the result" do
69
+ mock( @machine ).find_or_create_states_by_name( :initial ) { [:result] }
70
+ @event.targets= :initial
71
+ @event.targets.should == [:result]
72
+ end
73
+ end
74
+
75
+ describe "lathe" do
76
+ before do
77
+ @lathe = @event.lathe()
78
+ end
79
+
80
+ it "should return a StateFu::Lathe" do
81
+ @lathe.should be_kind_of( StateFu::Lathe )
82
+ end
83
+
84
+ it "should have the event's machine" do
85
+ @lathe.machine.should == @event.machine()
86
+ end
87
+
88
+ it "should have the event as the sprocket" do
89
+ @lathe.state_or_event.should == @event
90
+ end
91
+
92
+ end
93
+
94
+ describe '.from()' do
95
+ describe "given @event.from :initial, :to => :final" do
96
+ describe "setting attributes" do
97
+ before do
98
+ stub( @machine ).find_or_create_states_by_name( anything ) { |*a| raise(a.inspect) }
99
+ stub( @machine ).find_or_create_states_by_name( :initial ) { [@initial] }
100
+ stub( @machine ).find_or_create_states_by_name( :final ) { [@final] }
101
+ end
102
+
103
+ it "should call @machine.find_or_create_states_by_name() with :initial and :final" do
104
+ @event.from :initial, :to => :final
105
+ end
106
+
107
+ it "should set @event.origin to the returned array of origin states" do
108
+ @event.from :initial, :to => :final
109
+ @event.origins.should == [@initial]
110
+ end
111
+
112
+ it "should set @event.target to the returned array of target states" do
113
+ @event.from :initial, :to => :final
114
+ @event.targets.should == [@final]
115
+ end
116
+ end
117
+ end
118
+
119
+ describe "given @event.from <Array>, :to => <Array>" do
120
+ it "should call @machine.find_or_create_states_by_name() with both arrays" do
121
+ stub( @machine ).find_or_create_states_by_name(:initial, :start) do
122
+ [@initial, @start]
123
+ end
124
+ stub( @machine ).find_or_create_states_by_name(:final, :end) do
125
+ [@final, @end]
126
+ end
127
+
128
+ @event.from( [:initial, :start], :to => [:final, :end] )
129
+ end
130
+ end
131
+
132
+ describe "given @event.from :ALL, :to => :ALL" do
133
+ it "should set origins and targets to @machine.states" do
134
+ stub( @machine ).states() { [:all, :of, :them ] }
135
+ stub( @machine ).find_or_create_states_by_name(anything) do |x|
136
+ x
137
+ end
138
+ @event.from( :ALL, :to => :ALL )
139
+ @event.origins.should == [:all, :of, :them ]
140
+ @event.targets.should == [:all, :of, :them ]
141
+ end
142
+ end
143
+
144
+ end
145
+
146
+ describe '.to()' do
147
+ describe "given :final" do
148
+ it "should set @event.target to machine.find_or_create_states_by_name( :final )" do
149
+ mock( @machine ).find_or_create_states_by_name(:final) { [@final] }
150
+ @event.to :final
151
+ @event.targets.should == [@final]
152
+ end
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ describe 'origin_names' do
159
+ it "should return an array of state names in origin when origin is not nil" do
160
+ mock( @machine ).find_or_create_states_by_name(:initial) { [@initial] }
161
+ mock( @machine ).find_or_create_states_by_name(:final) { [@final] }
162
+ @event.from :initial, :to => :final
163
+ @event.origin.should == @initial
164
+ mock( @initial ).to_sym().times(any_times) { :initial }
165
+ @event.origin_names.should == [:initial]
166
+ end
167
+
168
+ it "should return nil when origin is nil" do
169
+ mock( @event ).origins().times(any_times) { nil }
170
+ @event.origin_names.should == nil
171
+ end
172
+
173
+ end
174
+
175
+ describe 'target_names' do
176
+ it "should return an array of state names in target when target is not nil" do
177
+ mock( @event ).targets.times( any_times ) { [@final] }
178
+ mock( @final ).to_sym { :final }
179
+ @event.target_names.should == [:final]
180
+ end
181
+
182
+ it "should return nil when target is nil" do
183
+ mock( @event ).targets().times(any_times) { nil }
184
+ @event.target_names.should == nil
185
+ end
186
+ end
187
+
188
+ describe 'to?' do
189
+ it "should return true given a symbol which is the name of a state in @target" do
190
+ mock( @event ).targets.times(any_times) { [StateFu::State.new(@machine,:a)] }
191
+ @event.to?( :a ).should == true
192
+ end
193
+
194
+ it "should return false given a symbol which is not the name of a state in @target" do
195
+ mock( @event ).targets.times(any_times) { [StateFu::State.new(@machine,:a)] }
196
+ @event.to?( :b ).should == false
197
+ end
198
+ end
199
+
200
+ describe 'from?' do
201
+ it "should return true given a symbol which is the name of a state in @origin" do
202
+ mock( @event ).origins.times(any_times) { [StateFu::State.new(@machine,:a)] }
203
+ @event.from?( :a ).should == true
204
+ end
205
+
206
+ it "should return nil given a symbol which is not the name of a state in @origin" do
207
+ mock( @event ).origins().times(any_times) { [StateFu::State.new(@machine,:a)] }
208
+ @event.from?( :b ).should == nil
209
+ end
210
+ end
211
+
212
+ end # describe instance methods
213
+ end # describe StateFu::Event
214
+ end
@@ -0,0 +1,82 @@
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
+ pending
22
+ e = StateFu::TransitionHalted.new( @transition )
23
+ e.should be_kind_of( StateFu::TransitionHalted )
24
+ end
25
+
26
+ it "should allow a custom message" do
27
+ pending
28
+ msg = 'helo'
29
+ e = StateFu::TransitionHalted.new( @transition, msg )
30
+ e.should be_kind_of( StateFu::TransitionHalted )
31
+ e.message.should == msg
32
+ end
33
+
34
+ it "should allow a message to be omitted" do
35
+ pending
36
+ e = StateFu::TransitionHalted.new( @transition )
37
+ e.should be_kind_of( StateFu::TransitionHalted )
38
+ e.message.should == StateFu::TransitionHalted::DEFAULT_MESSAGE
39
+ end
40
+
41
+ it "should allow access to the transition" do
42
+ pending
43
+ e = StateFu::TransitionHalted.new( @transition )
44
+ e.transition.should == @transition
45
+ end
46
+ end
47
+ end
48
+
49
+ describe StateFu::IllegalTransition do
50
+ before do
51
+ @binding = Object.new
52
+ @origin = Object.new
53
+ @event = Object.new
54
+ @target = Object.new
55
+ end
56
+
57
+ describe "constructor" do
58
+ it "should create an IllegalTransition given a binding, event, origin & target" do
59
+ pending
60
+ e = StateFu::IllegalTransition.new( @binding, @event, @origin, @target )
61
+ e.should be_kind_of( StateFu::IllegalTransition )
62
+ e.message.should == StateFu::IllegalTransition::DEFAULT_MESSAGE
63
+ end
64
+
65
+ it "should allow a custom message" do
66
+ pending
67
+ msg = 'helo'
68
+ e = StateFu::IllegalTransition.new( @binding, @event, @origin, @target, msg )
69
+ e.should be_kind_of( StateFu::IllegalTransition )
70
+ e.message.should == msg
71
+ end
72
+
73
+ it "should allow access to the binding, event, origin, and target" do
74
+ pending
75
+ e = StateFu::IllegalTransition.new( @binding, @event, @origin, @target )
76
+ e.binding.should == @binding
77
+ e.event.should == @event
78
+ e.origin.should == @origin
79
+ e.target.should == @target
80
+ end
81
+ end
82
+ end