state-fu 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
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,47 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe "extending bindings and transitions with Lathe#helper" do
4
+
5
+ include MySpecHelper
6
+
7
+ before(:each) do
8
+ reset!
9
+ make_pristine_class('Klass')
10
+
11
+ @machine = Klass.state_fu_machine do
12
+ state :normal, :colour => 'green'
13
+ state :bad, :colour => 'red'
14
+ event( :worsen, :colour => 'orange' ) { from :normal => :bad }
15
+ end
16
+ @obj = Klass.new
17
+ @binding = @obj.state_fu
18
+
19
+ end # before
20
+
21
+ describe "accessing sprocket options" do
22
+ describe "state#[]" do
23
+ it "should return state.options[key]" do
24
+ @machine.states[:normal][:colour].should == 'green'
25
+ end
26
+ end
27
+ describe "event#[]" do
28
+ it "should return event.options[key]" do
29
+ @machine.events[:worsen][:colour].should == 'orange'
30
+ end
31
+ end
32
+
33
+ describe "state#[]=" do
34
+ it "should update state.options" do
35
+ @machine.states[:normal][:flavour] = 'lime'
36
+ @machine.states[:normal][:flavour].should == 'lime'
37
+ end
38
+ end
39
+ describe "event#[]=" do
40
+ it "should update event.options" do
41
+ @machine.events[:worsen][:flavour] = 'orange'
42
+ @machine.events[:worsen][:flavour].should == 'orange'
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,101 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+
3
+ describe "extending bindings and transitions with Lathe#helper" do
4
+
5
+ include MySpecHelper
6
+
7
+ before(:each) do
8
+ reset!
9
+ make_pristine_class('Klass')
10
+ Klass.class_eval do
11
+ attr_accessor :ok
12
+ end
13
+
14
+ @machine = Klass.machine do
15
+ chain "a -a2b-> b -b2c-> c"
16
+ events.each do |e|
17
+ e.requires :ok
18
+ end
19
+ end
20
+
21
+ @obj = Klass.new
22
+ @binding = @obj.state_fu
23
+ @transition = @obj.state_fu.transition(:a2b)
24
+ end # before
25
+
26
+ #
27
+ #
28
+
29
+ describe StateFu::Transition do
30
+ describe "equality" do
31
+
32
+ it "should == the current_state" do
33
+ @transition.should == @transition.current_state
34
+ end
35
+
36
+ it "should != any other state" do
37
+ @transition.should_not == @transition.target
38
+ end
39
+
40
+ it "should == the current_state_name" do
41
+ @transition.should == @transition.current_state
42
+ end
43
+
44
+ it "should != any other State's name" do
45
+ @transition.should_not == @transition.target.name
46
+ end
47
+
48
+ describe "with an unaccepted transition" do
49
+ before do
50
+ # stub(@transition).accepted? { false }
51
+ end
52
+
53
+ it "should != true" do
54
+ @transition.should_not == true
55
+ end
56
+
57
+ it "should == false" do
58
+ @transition.should == false
59
+ end
60
+
61
+ it "should not === true" do
62
+ @transition.should_not === true
63
+ end
64
+
65
+ it "should === false" do
66
+ @transition.should === false
67
+ end
68
+
69
+ it "should not be nil?" do
70
+ @transition.nil?.should be_false
71
+ end
72
+ end
73
+
74
+
75
+ describe "with an accepted transition" do
76
+ before do
77
+ @obj.ok = true
78
+ @transition.fire!
79
+ @transition.should be_accepted
80
+ end
81
+ it "should == true" do
82
+ @transition.should == true
83
+ end
84
+
85
+ it "should not == false" do
86
+ @transition.should_not == false
87
+ end
88
+
89
+ it "should === true" do
90
+ @transition.should === true
91
+ end
92
+
93
+ it "should not === false" do
94
+ @transition.should_not === false
95
+ end
96
+
97
+ end
98
+ end
99
+
100
+ end
101
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require File.join(File.dirname(__FILE__),'spec_helper')
3
+
4
+ begin
5
+ require 'rr'
6
+ rescue LoadError => e
7
+ STDERR.puts "The '#{gem_name}' gem is required to run StateFu's specs. Please install it by running (as root):\ngem install #{gem_name}\n\n"
8
+ exit 1;
9
+ end
10
+
11
+ Spec::Runner.configure do |config|
12
+ config.mock_with :rr
13
+ end
@@ -0,0 +1,202 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe "an ActiveRecord model with StateFu included:" do
4
+
5
+ include MySpecHelper
6
+
7
+ before(:each) do
8
+ reset!
9
+ prepare_active_record() do
10
+ def self.up
11
+ create_table :example_records do |t|
12
+ t.string :name, :null => false
13
+ t.string :state_fu_field, :null => false
14
+ t.string :description
15
+ t.string :status
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
20
+
21
+ # class ExampleRecord < ActiveRecord::Base
22
+ make_pristine_class( 'ExampleRecord', ActiveRecord::Base )
23
+ ExampleRecord.class_eval do
24
+ validates_presence_of :name
25
+ end
26
+ # end class ExampleRecord
27
+ end
28
+
29
+ it "should be a subclass of ActiveRecord::Base" do
30
+ ExampleRecord.superclass.should == ActiveRecord::Base
31
+ end
32
+
33
+ describe "when the ActiveRecord model has no table yet (eg before migrations)" do
34
+ before do
35
+ make_pristine_class('TableMissingClass', ActiveRecord::Base )
36
+ TableMissingClass.class_eval do
37
+ state_fu_machine() { }
38
+ end
39
+ end
40
+
41
+ it "should not raise an error when the persister is instantiated" do
42
+ lambda { TableMissingClass.columns }.should raise_error
43
+ lambda { TableMissingClass.state_fu_machine }.should_not raise_error
44
+ end
45
+
46
+ end
47
+ describe "when the default machine is defined with no field_name specified" do
48
+ before do
49
+ ExampleRecord.class_eval do
50
+ state_fu_machine do
51
+ state :initial do
52
+ event( :change, :to => :final ) { after :save! }
53
+ end
54
+ end
55
+ end
56
+ @ex = ExampleRecord.new( :name => "exemplar" )
57
+ end # before
58
+
59
+ it "should have an active_record string column 'state_fu_field' " do
60
+ col = ExampleRecord.columns.detect {|c| c.name == "state_fu_field" }
61
+ col.type.should == :string
62
+ end
63
+
64
+ describe "calling :save! via an event's after hook" do
65
+ it "should save the record with the new state persisted via the DB" do
66
+ @ex.change!
67
+ @ex.state_fu.name.should == :final
68
+ @ex.state_fu_field.should == 'final'
69
+ @ex.reload
70
+ @ex.state_fu_field.should == 'final'
71
+ @ex.state_fu.name.should == :final
72
+ end
73
+ end
74
+
75
+ describe "StateFu::Persistence.active_record_column?" do
76
+ it "should return true for ExampleRecord, :state_fu_field" do
77
+ StateFu::Persistence.active_record_column?( ExampleRecord, :state_fu_field ).should == true
78
+ end
79
+
80
+ it "should return true for ExampleRecord, :status" do
81
+ StateFu::Persistence.active_record_column?( ExampleRecord, :status ).should == true
82
+ end
83
+
84
+ it "should return false for ExampleRecord, :not_a_column" do
85
+ StateFu::Persistence.active_record_column?( ExampleRecord, :not_a_column ).should == false
86
+ end
87
+ it "should not clobber activerecord accessors" do
88
+ @ex.noodle! rescue nil
89
+ # lambda { @ex.description }.should_not raise_error()
90
+ @ex.description.should be_nil
91
+ @ex.description= 'foo'
92
+ @ex.description.should == 'foo'
93
+ end
94
+
95
+ it "should have an active_record string column 'state_fu_field' " do
96
+ col = ExampleRecord.columns.detect {|c| c.name == "state_fu_field" }
97
+ col.type.should == :string
98
+ end
99
+ end
100
+
101
+ it "should have an active_record persister with the default field_name 'state_fu_field' " do
102
+ @ex.state_fu
103
+ @ex.state_fu.should be_kind_of( StateFu::Binding )
104
+ @ex.state_fu.persister.should be_kind_of( StateFu::Persistence::ActiveRecord )
105
+ @ex.state_fu.persister.field_name.should == :state_fu_field
106
+ end
107
+
108
+
109
+ # this ensures state_fu initializes the field before create to
110
+ # satisfy the not null constraint
111
+ describe "automagic state_fu! before_save filter and validations" do
112
+
113
+ it "should call state_fu! before a record is created" do
114
+ @ex.should be_new_record
115
+ mock.proxy( @ex ).state_fu!.at_least( 1 ) { }
116
+ @ex.save!
117
+ end
118
+
119
+ it "should call state_fu! before a record is updated" do
120
+ @ex.should be_new_record
121
+ mock.proxy( @ex ).state_fu!.at_least( 1 ) { }
122
+ @ex.save!
123
+ end
124
+
125
+ it "should fail to save if state_fu! does not instantiate the binding before create" do
126
+ mock( @ex ).state_fu!.at_least( 1 ) { }
127
+ lambda { @ex.save! }.should raise_error( ActiveRecord::StatementInvalid )
128
+ @ex.state_fu_field.should == nil
129
+ end
130
+
131
+ it "should create a record given only a name, with the field set to the initial state" do
132
+ ex = ExampleRecord.new( :name => "exemplar" )
133
+ ex.should be_valid
134
+ ex.state_fu_field.should == nil
135
+ ex.save!
136
+ ex.should_not be_new_record
137
+ ex.state_fu_field.should == 'initial'
138
+ ex.state_fu.state.name.should == :initial
139
+ end
140
+
141
+ it "should update the field after a transition is completed" do
142
+ ex = ExampleRecord.create!( :name => "exemplar" )
143
+ ex.state_fu.state.name.should == :initial
144
+ ex.state_fu_field.should == 'initial'
145
+ t = ex.state_fu.change!
146
+ t.should be_accepted
147
+ ex.state_fu.state.name.should == :final
148
+ ex.state_fu_field.should == 'final'
149
+ ex.attributes['state_fu_field'].should == 'final'
150
+ ex.save!
151
+ end
152
+
153
+ describe "a saved record whose state is not the default" do
154
+ before do
155
+ @r = ExampleRecord.create!( :name => "exemplar" )
156
+ @r.change!
157
+ @r.state_fu_field.should == 'final'
158
+ @r.save!
159
+ end
160
+
161
+ it "should be reconstituted with the correct state" do
162
+ r = ExampleRecord.find( @r.id )
163
+ r.state_fu.should be_kind_of( StateFu::Binding )
164
+ r.state_fu.current_state.should be_kind_of( StateFu::State )
165
+ r.state_fu.current_state.should == ExampleRecord.state_fu_machine.states[:final]
166
+ end
167
+ end # saved record after transition
168
+
169
+ describe "when a second machine named :status is defined with :field_name => 'status' " do
170
+ before do
171
+ ExampleRecord.state_fu_machine(:status, :field_name => 'status') do
172
+ event( :go, :from => :initial, :to => :final )
173
+ end
174
+ @ex = ExampleRecord.new()
175
+ end
176
+
177
+ it "should have a binding for .status" do
178
+ @ex.status.should be_kind_of( StateFu::Binding )
179
+ end
180
+
181
+ it "should have an ActiveRecord persister with the field_name :status" do
182
+ @ex.status.persister.should be_kind_of( StateFu::Persistence::ActiveRecord )
183
+ @ex.status.persister.field_name.should == :status
184
+ end
185
+
186
+ it "should have a value of nil for the status field before state_fu is called" do
187
+ @ex.read_attribute('status').should be_nil
188
+ end
189
+
190
+ it "should have the ActiveRecord setter method .status=" do
191
+ @ex.status= 'damp'
192
+ @ex.read_attribute(:status).should == 'damp'
193
+ end
194
+
195
+ it "should raise StateFu::InvalidState if the status field is set to a bad value and .status is called" do
196
+ @ex.status= 'damp'
197
+ lambda { @ex.status }.should raise_error( StateFu::InvalidStateName )
198
+ end
199
+ end
200
+ end # second machine
201
+ end # with before_create filter
202
+ end # default machine
@@ -0,0 +1,41 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ module BenchGrinder
4
+ def snark
5
+ end
6
+ end
7
+
8
+ describe "extending StateFu::Lathe" do
9
+ include MySpecHelper
10
+
11
+ describe "helpers" do
12
+ before do
13
+ reset!
14
+ make_pristine_class('Klass')
15
+ @machine = Klass.state_fu_machine() do
16
+ state :init
17
+ end
18
+ end # before
19
+
20
+ describe "lathe.helper" do
21
+ it "should add the arguments to the machine's collection of helpers" do
22
+ @machine.should respond_to(:helpers)
23
+ @machine.helpers.should be_empty
24
+ @machine.lathe do
25
+ helper :bench_grinder
26
+ end
27
+ @machine.helpers.should_not be_empty
28
+ @machine.helpers.should include(:bench_grinder)
29
+ end
30
+
31
+ it "should extend the binding with the helper's methods" do
32
+ @machine.lathe do
33
+ helper :bench_grinder
34
+ end
35
+ @machine.helpers.should include(:bench_grinder)
36
+ @obj = Klass.new
37
+ @obj.state_fu.should respond_to(:snark)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,117 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+
4
+ ##
5
+ ##
6
+ ##
7
+
8
+ describe "A pristine class Klass with StateFu included:" do
9
+ include MySpecHelper
10
+ before(:each) do
11
+ make_pristine_class 'Klass'
12
+ end
13
+
14
+ it "should return a new Machine bound to the class given Klass.state_fu_machine()" do
15
+ Klass.should respond_to(:state_fu_machine)
16
+ Klass.state_fu_machine.should be_kind_of(StateFu::Machine)
17
+ machine = Klass.state_fu_machine
18
+ Klass.state_fu_machine.should == machine
19
+ end
20
+
21
+ it "should return {} given Klass.state_fu_machines" do
22
+ Klass.should respond_to(:state_fu_machines)
23
+ Klass.state_fu_machines.should == {}
24
+ end
25
+
26
+ ##
27
+ ##
28
+ ##
29
+
30
+ describe "Having called Klass.state_fu_machine() with an empty block:" do
31
+ before(:each) do
32
+ Klass.state_fu_machine do
33
+ end
34
+ end
35
+
36
+ it "should return a StateFu::Machine given Klass.state_fu_machine()" do
37
+ Klass.should respond_to(:state_fu_machine)
38
+ Klass.state_fu_machine.should_not be_nil
39
+ Klass.state_fu_machine.should be_kind_of( StateFu::Machine )
40
+ end
41
+
42
+ it "should return { :default => <StateFu::Machine> } given Klass.state_fu_machines()" do
43
+ Klass.should respond_to(:state_fu_machines)
44
+ machines = Klass.state_fu_machines()
45
+ machines.should be_kind_of(Hash)
46
+ machines.should_not be_empty
47
+ machines.length.should == 1
48
+ machines.keys.should == [StateFu::DEFAULT]
49
+ machines.values.first.should be_kind_of( StateFu::Machine )
50
+ end
51
+
52
+ describe "Having called Klass.state_fu_machine(:two) with an empty block:" do
53
+ before(:each) do
54
+ Klass.state_fu_machine(:two) do
55
+ end
56
+ end
57
+
58
+ it "should return a StateFu::Machine given Klass.state_fu_machine(:two)" do
59
+ Klass.should respond_to(:state_fu_machine)
60
+ Klass.state_fu_machine(:two).should_not be_nil
61
+ Klass.state_fu_machine(:two).should be_kind_of( StateFu::Machine )
62
+ end
63
+
64
+ it "should return a new Machine given Klass.state_fu_machine(:three)" do
65
+ Klass.should respond_to(:state_fu_machine)
66
+ Klass.state_fu_machine(:three).should be_kind_of( StateFu::Machine )
67
+ three = Klass.state_fu_machine(:three)
68
+ Klass.state_fu_machines[:three].should == three
69
+ Klass.state_fu_machine(:three).should == three
70
+ end
71
+
72
+ it "should return { :default => <StateFu::Machine>, :two => <StateFu::Machine> } given Klass.state_fu_machines()" do
73
+ Klass.should respond_to(:state_fu_machines)
74
+ machines = Klass.state_fu_machines()
75
+ machines.should be_kind_of(Hash)
76
+ machines.should_not be_empty
77
+ machines.length.should == 2
78
+ machines.keys.should include StateFu::DEFAULT
79
+ machines.keys.should include :two
80
+ machines.values.length.should == 2
81
+ machines.values.each { |v| v.should be_kind_of( StateFu::Machine ) }
82
+ end
83
+
84
+ it "should return [DEFAULT, :two] give Klass.state_fu_machines.keys" do
85
+ Klass.should respond_to(:state_fu_machines)
86
+ Klass.state_fu_machines.keys.should =~ [StateFu::DEFAULT, :two]
87
+ end
88
+ end
89
+
90
+ describe "An empty class Child which inherits from Klass" do
91
+ before() do
92
+ Object.send(:remove_const, 'Child' ) if Object.const_defined?( 'Child' )
93
+ class Child < Klass
94
+ end
95
+ end
96
+
97
+ # sorry, Lamarckism not supported
98
+ it "does NOT inherit it's parent class' Machines !!" do
99
+ Child.state_fu_machine.should_not == Klass.state_fu_machine
100
+ end
101
+
102
+ it "should know the Machine after calling Klass.state_fu_machine.bind!( Child )" do
103
+ Child.state_fu_machine.should_not == Klass.state_fu_machine
104
+ Klass.state_fu_machine.bind!( Child )
105
+ Klass.state_fu_machines.should == { StateFu::DEFAULT => Klass.state_fu_machine }
106
+ Child.state_fu_machine.should == Klass.state_fu_machine
107
+ Klass.state_fu_machine.bind!( Child, :snoo )
108
+ Child.state_fu_machines.should == {
109
+ StateFu::DEFAULT => Klass.state_fu_machine,
110
+ :snoo => Klass.state_fu_machine
111
+ }
112
+ Child.state_fu_machine(:snoo).should == Klass.state_fu_machine
113
+ end
114
+
115
+ end
116
+ end
117
+ end