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,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