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