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,74 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ ##
4
+ ##
5
+ ##
6
+
7
+ describe "Adding events to a Machine outside a state block" do
8
+
9
+ include MySpecHelper
10
+
11
+ describe "When there is an empty machine" do
12
+ before do
13
+ reset!
14
+ make_pristine_class 'Klass'
15
+ Klass.state_fu_machine() { }
16
+ end
17
+
18
+ describe "calling Klass.state_fu_machine().events" do
19
+ it "should return []" do
20
+ Klass.state_fu_machine().events.should == []
21
+ end
22
+ end
23
+
24
+ describe "calling event(:die){ from :dead, :to => :alive } in a Klass.state_fu_machine()" do
25
+ before do
26
+ Klass.state_fu_machine do
27
+ event :die do # arity == 0
28
+ from :dead, :to => :alive
29
+ end
30
+ end
31
+ end
32
+
33
+ it "should require a name when calling machine.event()" do
34
+ lambda { Klass.state_fu_machine(){ event {} } }.should raise_error(ArgumentError)
35
+ end
36
+
37
+ it "should add 2 states to the machine called: [:dead, :alive] " do
38
+ Klass.state_fu_machine.state_names.should == [:dead, :alive]
39
+ Klass.state_fu_machine.states.length.should == 2
40
+ Klass.state_fu_machine.states.each { |s| s.should be_kind_of(StateFu::State) }
41
+ Klass.state_fu_machine.states.map(&:name).sort.should == [:alive, :dead]
42
+ end
43
+
44
+ describe "the <StateFu::Event> created" do
45
+ it "should be accessible through Klass.state_fu_machine.events" do
46
+ Klass.state_fu_machine.events.should be_kind_of(Array)
47
+ Klass.state_fu_machine.events.length.should == 1
48
+ Klass.state_fu_machine.events.first.should be_kind_of( StateFu::Event )
49
+ Klass.state_fu_machine.events.first.name.should == :die
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ # arity of blocks is optional, thanks to magic fairy dust ;)
56
+ describe "calling event(:die){ |s| s.from :dead, :to => :alive } in a Klass.state_fu_machine()" do
57
+ before do
58
+ Klass.state_fu_machine do
59
+ event :die do |s|
60
+ s.from :dead, :to => :alive
61
+ end
62
+ end
63
+ end
64
+
65
+ it "should add 2 states to the machine called [:dead, :alive] " do
66
+ Klass.state_fu_machine.state_names.should == [:dead, :alive]
67
+ Klass.state_fu_machine.states.length.should == 2
68
+ Klass.state_fu_machine.states.each { |s| s.should be_kind_of( StateFu::State ) }
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+
@@ -0,0 +1,133 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ # require 'activesupport'
4
+ # require 'activerecord'
5
+
6
+ describe "Document" do
7
+ include MySpecHelper
8
+ before do
9
+ reset!
10
+ prepare_active_record do
11
+ def self.up
12
+ create_table :documents do |t|
13
+ t.string :name
14
+ t.string :author
15
+ t.string :status_field
16
+ end
17
+ end
18
+ end
19
+ make_pristine_class('Document', ActiveRecord::Base )
20
+ Document.class_eval do
21
+ include StateFu
22
+
23
+ attr_accessor :author
24
+
25
+ def update_rss
26
+ # puts "new feed!"
27
+ end
28
+
29
+ state_fu_machine( :status ) do
30
+ state :draft do
31
+ event :publish, :to => :published
32
+ end
33
+
34
+ state :published do
35
+ on_entry :update_rss
36
+ requires :author
37
+ end
38
+
39
+ event :delete, :from => :ALL, :to => :deleted do
40
+ execute :destroy
41
+ end
42
+
43
+ states do
44
+ accepted :save!
45
+ end
46
+ end
47
+ end
48
+
49
+ @doc = Document.new
50
+ # @doc.status
51
+ end
52
+
53
+ describe "a new document with no attributes" do
54
+
55
+ it "should have a status.name of :draft" do
56
+ @doc.status.name.should == :draft
57
+ end
58
+
59
+ it "should have no author" do
60
+ @doc.author.should be_nil
61
+ end
62
+
63
+ it "should raise a RequirementError when publish! is called" do
64
+ @doc.status.name.should == :draft
65
+ lambda { @doc.status.publish! }.should raise_error( StateFu::RequirementError )
66
+ begin
67
+ @doc.status.publish!
68
+ rescue StateFu::RequirementError => e
69
+ e.message.should =~ /[:author]/
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "a new document with an author" do
75
+ before do
76
+ @doc.author = "Susan"
77
+ end
78
+
79
+ it "should have a status.name of :draft" do
80
+ @doc.status.name.should == :draft
81
+ end
82
+
83
+ it "should have an author" do
84
+ @doc.author.should_not be_nil
85
+ end
86
+
87
+ it "should not raise an error when publish! is called" do
88
+ lambda { @doc.status.publish! }.should_not raise_error( )
89
+ end
90
+
91
+ it "should call update_rss when publish! is called" do
92
+ mock( @doc ).update_rss(anything) {}
93
+ @doc.status.publish!
94
+ end
95
+
96
+ it "should have the state name :published after .publish! is called" do
97
+ @doc.status.publish!
98
+ @doc.status.current_state_name.should == :published
99
+ end
100
+
101
+ describe "status_field attribute" do
102
+
103
+ # need to think about this
104
+ #
105
+ # it "should be private in ruby 1.8 and 1.9" do
106
+ # lambda { @doc.status_field }.should raise_error()
107
+ # end
108
+
109
+ it "should be defined before state_fu is called" do
110
+ @doc.send( :status_field ).should == 'draft'
111
+ end
112
+
113
+ it "should have an initial value of 'draft'" do
114
+ @doc.instance_eval { status_field }.should == "draft"
115
+ end
116
+
117
+ it "should be set to 'published' after publish! is called successfully" do
118
+ @doc.status.publish!
119
+ @doc.instance_eval { status_field }.should == "published"
120
+ end
121
+ end # status_field
122
+ end # with author
123
+
124
+ describe "delete!" do
125
+
126
+ it "should execute destroy()" do
127
+ mock( @doc ).destroy(anything) {}
128
+ @doc.status.delete!
129
+ end
130
+
131
+ end
132
+
133
+ end
@@ -0,0 +1,88 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe String do
4
+ include MySpecHelper
5
+ before do
6
+ reset!
7
+ String.class_eval do
8
+ include StateFu
9
+
10
+ def sanitize_for_shell!
11
+ gsub!(/([\\\t\| &`<>)('"])/) { |s| '\\' << s }
12
+ end
13
+
14
+ def dirty?
15
+ shell.name == :dirty
16
+ end
17
+
18
+ def clean?
19
+ shell.name == :clean
20
+ end
21
+
22
+ def shell_escape!
23
+ shell.escape!
24
+ end
25
+
26
+ def shell_escape
27
+ #
28
+ klone = clone
29
+ begin
30
+ klone.shell.escape!
31
+ rescue StateFu::IllegalTransition
32
+ end
33
+ klone
34
+ end
35
+
36
+ state_fu_machine (:shell) do
37
+ event(:escape, :from => {:dirty => :clean}) do
38
+ execute :sanitize_for_shell!
39
+ end
40
+ end
41
+ end # String
42
+ @str = "; nohup 'rm -rf /opt' &"
43
+ end # before
44
+
45
+ it "should initially be dirty" do
46
+ @str.dirty?.should be_true
47
+ end
48
+
49
+ it "should call sanitize_for_shell! when shell.escape! is called, and be clean afterwards " do
50
+ @str.should be_dirty
51
+ @str.should_not be_clean
52
+ mock( @str ).sanitize_for_shell!(anything) {}
53
+ @str.shell.escape!
54
+ @str.should_not be_dirty
55
+ @str.should be_clean
56
+ end
57
+
58
+ it "should raise an IllegalTransition if shell.escape! is called more than once" do
59
+ @str.shell.escape!
60
+ @str.shell.state_name.should == :clean
61
+
62
+ lambda { @str.shell.escape! }.should raise_error( StateFu::IllegalTransition )
63
+ end
64
+
65
+ it "should modify the string when shell.escape is called" do
66
+ original = @str.dup
67
+ original.should == @str
68
+ @str.shell.escape!
69
+ original.should_not == @str
70
+ end
71
+
72
+ it "should modify the string when shell.escape! is called" do
73
+ original = @str.dup
74
+ original.should == @str
75
+ @str.shell.escape!
76
+ original.should_not == @str
77
+ end
78
+
79
+ it "should not modify the original string when shell_escape() is called" do
80
+ original = @str.dup
81
+ original.should == @str
82
+ clean_copy = @str.shell_escape()
83
+ clean_copy.should be_clean
84
+ @str.should be_dirty
85
+ original.should == @str
86
+ end
87
+
88
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ ##
4
+ ##
5
+ ##
6
+
7
+ describe "An instance of Klass with StateFu included:" do
8
+ include MySpecHelper
9
+ before(:each) do
10
+ make_pristine_class 'Klass'
11
+ @k = Klass.new()
12
+ end
13
+
14
+ describe "when no machine is defined" do
15
+ it "should raise an ArgumentError given .state_fu()" do
16
+ lambda { @k.state_fu() }.should raise_error(ArgumentError)
17
+ end
18
+
19
+ it "should return {} given .bindings()" do
20
+ @k.state_fu_bindings.should == {}
21
+ end
22
+
23
+ it "should return [] given .state_fu!()" do
24
+ @k.state_fu!.should == []
25
+ end
26
+ end # no machine
27
+
28
+ describe "when an empty machine is defined for the class with the default name:" do
29
+ before(:each) do
30
+ Klass.state_fu_machine() {}
31
+ end
32
+
33
+ it "should return a StateFu::Binding given .state_fu()" do
34
+ @k.state_fu_binding.should be_kind_of( StateFu::Binding )
35
+ end
36
+
37
+ describe "before a binding is instantiated by calling .state_fu() or .state_fu!" do
38
+ it "should return {} given .bindings()" do
39
+ @k.bindings.should == {}
40
+ end
41
+ end
42
+
43
+ describe "after a binding is instantiated with .state_fu()" do
44
+ before do
45
+ @k.state_fu()
46
+ end
47
+
48
+ it "should return { :state_fu => <StateFu::Binding>} given .bindings()" do
49
+ @k.bindings().length.should == 1
50
+ @k.bindings().keys.should == [StateFu::DEFAULT]
51
+ @k.bindings().values.first.should be_kind_of( StateFu::Binding )
52
+ end
53
+ end
54
+
55
+ describe "after .state_fu!()" do
56
+ it "should return { :state_fu => <StateFu::Binding>} given .bindings()" do
57
+ @k.state_fu!()
58
+ @k.bindings().length.should == 1
59
+ @k.bindings().keys.should == [StateFu::DEFAULT]
60
+ @k.bindings().values.first.should be_kind_of( StateFu::Binding )
61
+ end
62
+ end
63
+
64
+ it "should return [<StateFu::Binding>] given .state_fu!()" do
65
+ @k.state_fu!.length.should == 1
66
+ @k.state_fu!.first.should be_kind_of( StateFu::Binding )
67
+ end
68
+
69
+ describe "when there is an empty machine called :two for the class" do
70
+ before(:each) do
71
+ Klass.state_fu_machine(:two) {}
72
+ end
73
+
74
+ it "should return the same Binding given .state_fu() and .state_fu(:state_fu)" do
75
+ @k.state_fu().should be_kind_of( StateFu::Binding )
76
+ @k.state_fu().should == @k.state_fu(StateFu::DEFAULT)
77
+ end
78
+
79
+ it "should return a StateFu::Binding for the machine called :two given .state_fu(:two)" do
80
+ @k.state_fu(:two).should be_kind_of( StateFu::Binding )
81
+ @k.state_fu(:two).should_not == @k.state_fu(StateFu::DEFAULT)
82
+ @k.state_fu(:two).machine.should == Klass.state_fu_machine(:two)
83
+ end
84
+
85
+ it "should raise an ArgumentError when .state_fu() is called with the name of a machine which doesn't exist" do
86
+ lambda { @k.state_fu(:hibiscus) }.should raise_error(ArgumentError)
87
+ end
88
+
89
+ it "should return an array of the two StateFu::Bindings given .state_fu!" do
90
+ @k.state_fu!.should be_kind_of( Array )
91
+ @k.state_fu!.length.should == 2
92
+ @k.state_fu!.each { |m| m.should be_kind_of( StateFu::Binding ) }
93
+ @k.state_fu!.map(&:method_name).sort_by(&:to_s).should == [StateFu::DEFAULT, :two]
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,67 @@
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.tool" do
21
+
22
+ it "should add the arguments to the machine's collection of tools" do
23
+ @machine.should respond_to(:tools)
24
+ @machine.tools.should be_empty
25
+ @machine.lathe do
26
+ tool :bench_grinder
27
+ end
28
+ @machine.tools.should_not be_empty
29
+ @machine.tools.should include(:bench_grinder)
30
+ end
31
+
32
+ it "should extend the machine's lathe" do
33
+ @machine.lathe do
34
+ tool :bench_grinder
35
+ snark()
36
+ end
37
+ @machine.lathe.should respond_to( :snark )
38
+ @machine.lathe.snark
39
+ end
40
+
41
+ it "should extend the machine's lathe for state and events" do
42
+ @machine.lathe do
43
+ tool :bench_grinder
44
+ snark()
45
+ state :grinding do
46
+ snark()
47
+ event :grind do
48
+ snark()
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ it "should not extend another machine's lathe" do
55
+ @machine.lathe do
56
+ tool :bench_grinder
57
+ snark()
58
+ end
59
+ m2 = Klass.state_fu_machine(:two) do
60
+ end
61
+ lambda { m2.lathe.snark }.should raise_error( NoMethodError )
62
+ end
63
+
64
+ end # tool
65
+
66
+ end
67
+ end