simple_state_machine 0.5.3 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.rdoc +23 -0
  3. data/README.rdoc +154 -100
  4. data/lib/simple_state_machine/active_record.rb +2 -62
  5. data/lib/simple_state_machine/decorator/active_record.rb +68 -0
  6. data/lib/simple_state_machine/decorator/default.rb +91 -0
  7. data/lib/simple_state_machine/railtie.rb +1 -1
  8. data/lib/simple_state_machine/simple_state_machine.rb +8 -251
  9. data/lib/simple_state_machine/state_machine.rb +88 -0
  10. data/lib/simple_state_machine/state_machine_definition.rb +72 -0
  11. data/lib/simple_state_machine/tools/graphviz.rb +21 -0
  12. data/lib/simple_state_machine/tools/inspector.rb +44 -0
  13. data/lib/simple_state_machine/transition.rb +40 -0
  14. data/lib/simple_state_machine/version.rb +1 -1
  15. data/lib/simple_state_machine.rb +13 -3
  16. data/lib/tasks/graphviz.rake +31 -0
  17. metadata +37 -150
  18. data/.gitignore +0 -1
  19. data/.rspec +0 -3
  20. data/Gemfile +0 -4
  21. data/Rakefile +0 -28
  22. data/autotest/discover.rb +0 -1
  23. data/examples/conversation.rb +0 -33
  24. data/examples/lamp.rb +0 -21
  25. data/examples/relationship.rb +0 -87
  26. data/examples/traffic_light.rb +0 -17
  27. data/examples/user.rb +0 -37
  28. data/lib/tasks/graphiz.rake +0 -13
  29. data/rails/graphiz.rake +0 -16
  30. data/simple_state_machine.gemspec +0 -31
  31. data/spec/active_record_spec.rb +0 -223
  32. data/spec/decorator_spec.rb +0 -195
  33. data/spec/examples_spec.rb +0 -60
  34. data/spec/mountable_spec.rb +0 -24
  35. data/spec/simple_state_machine_spec.rb +0 -129
  36. data/spec/spec_helper.rb +0 -7
  37. data/spec/state_machine_definition_spec.rb +0 -89
  38. data/spec/state_machine_spec.rb +0 -26
@@ -1,223 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- require "rubygems"
4
- require "bundler"
5
- Bundler.require
6
- #Bundler.setup(:test)#, :activerecord)
7
- require 'active_record'
8
- require 'examples/user'
9
-
10
- ActiveRecord::Base.logger = Logger.new "test.log"
11
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
12
- :database => ":memory:")
13
-
14
- def setup_db
15
- ActiveRecord::Schema.define(:version => 1) do
16
- create_table :users do |t|
17
- t.column :id, :integer
18
- t.column :name, :string
19
- t.column :state, :string
20
- t.column :activation_code, :string
21
- t.column :created_at, :datetime
22
- t.column :updated_at, :datetime
23
- end
24
- create_table :tickets do |t|
25
- t.column :id, :integer
26
- t.column :ssm_state, :string
27
- end
28
- end
29
- end
30
-
31
- def teardown_db
32
- ActiveRecord::Base.connection.tables.each do |table|
33
- ActiveRecord::Base.connection.drop_table(table)
34
- end
35
- end
36
-
37
- class Ticket < ActiveRecord::Base
38
- extend SimpleStateMachine::ActiveRecord
39
-
40
- state_machine_definition.state_method = :ssm_state
41
-
42
- def after_initialize
43
- self.ssm_state ||= 'open'
44
- end
45
-
46
- event :close, :open => :closed
47
- end
48
-
49
- describe ActiveRecord do
50
-
51
- before do
52
- setup_db
53
- end
54
-
55
- after do
56
- teardown_db
57
- end
58
-
59
- it "has a default state" do
60
- User.new.should be_new
61
- end
62
-
63
- # TODO needs nesting/grouping, seems to have some duplication
64
-
65
- describe "event_and_save" do
66
- it "persists transitions" do
67
- user = User.create!(:name => 'name')
68
- user.invite_and_save.should == true
69
- User.find(user.id).should be_invited
70
- User.find(user.id).activation_code.should_not be_nil
71
- end
72
-
73
- it "persist transitions even when state is attr_protected" do
74
- user_class = Class.new(User)
75
- user_class.instance_eval { attr_protected :state }
76
- user = user_class.create!(:name => 'name', :state => 'x')
77
- user.should be_new
78
- user.invite_and_save
79
- user.reload.should be_invited
80
- end
81
-
82
- it "persists transitions when using send and a symbol" do
83
- user = User.create!(:name => 'name')
84
- user.send(:invite_and_save).should == true
85
- User.find(user.id).should be_invited
86
- User.find(user.id).activation_code.should_not be_nil
87
- end
88
-
89
- it "raises an error if an invalid state_transition is called" do
90
- user = User.create!(:name => 'name')
91
- expect {
92
- user.confirm_invitation_and_save 'abc'
93
- }.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
94
- "You cannot 'confirm_invitation' when state is 'new'")
95
- end
96
-
97
- it "returns false and keeps state if record is invalid" do
98
- user = User.new
99
- user.should be_new
100
- user.should_not be_valid
101
- user.invite_and_save.should == false
102
- user.should be_new
103
- end
104
-
105
- it "returns false, keeps state and keeps errors if event adds errors" do
106
- user = User.create!(:name => 'name')
107
- user.invite_and_save!
108
- user.should be_invited
109
- user.confirm_invitation_and_save('x').should == false
110
- user.should be_invited
111
- user.errors.entries.should == [['activation_code', 'is invalid']]
112
- end
113
-
114
- end
115
-
116
- describe "event_and_save!" do
117
-
118
- it "persists transitions" do
119
- user = User.create!(:name => 'name')
120
- user.invite_and_save!.should == true
121
- User.find(user.id).should be_invited
122
- User.find(user.id).activation_code.should_not be_nil
123
- end
124
-
125
- it "persist transitions even when state is attr_protected" do
126
- user_class = Class.new(User)
127
- user_class.instance_eval { attr_protected :state }
128
- user = user_class.create!(:name => 'name', :state => 'x')
129
- user.should be_new
130
- user.invite_and_save!
131
- user.reload.should be_invited
132
- end
133
-
134
- it "raises an error if an invalid state_transition is called" do
135
- user = User.create!(:name => 'name')
136
- expect {
137
- user.confirm_invitation_and_save! 'abc'
138
- }.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
139
- "You cannot 'confirm_invitation' when state is 'new'")
140
- end
141
-
142
- it "raises a RecordInvalid and keeps state if record is invalid" do
143
- user = User.new
144
- user.should be_new
145
- user.should_not be_valid
146
- expect {
147
- user.invite_and_save!
148
- }.to raise_error(ActiveRecord::RecordInvalid,
149
- "Validation failed: Name can't be blank")
150
- user.should be_new
151
- end
152
-
153
- it "raises a RecordInvalid and keeps state if event adds errors" do
154
- user = User.create!(:name => 'name')
155
- user.invite_and_save!
156
- user.should be_invited
157
- expect {
158
- user.confirm_invitation_and_save!('x')
159
- }.to raise_error(ActiveRecord::RecordInvalid,
160
- "Validation failed: Activation code is invalid")
161
- user.should be_invited
162
- end
163
-
164
- end
165
-
166
- describe "event" do
167
- it "persists transitions" do
168
- user = User.create!(:name => 'name')
169
- user.invite.should == true
170
- User.find(user.id).should be_invited
171
- User.find(user.id).activation_code.should_not be_nil
172
- end
173
-
174
- it "returns false, keeps state and keeps errors if event adds errors" do
175
- user = User.create!(:name => 'name')
176
- user.invite_and_save!
177
- user.should be_invited
178
- user.confirm_invitation('x').should == false
179
- user.should be_invited
180
- user.errors.entries.should == [['activation_code', 'is invalid']]
181
- end
182
-
183
- end
184
-
185
- describe "event!" do
186
-
187
- it "persists transitions" do
188
- user = User.create!(:name => 'name')
189
- user.invite!.should == true
190
- User.find(user.id).should be_invited
191
- User.find(user.id).activation_code.should_not be_nil
192
- end
193
-
194
- it "raises a RecordInvalid and keeps state if record is invalid" do
195
- user = User.new
196
- user.should be_new
197
- user.should_not be_valid
198
- expect { user.invite! }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name can't be blank")
199
- user.should be_new
200
- end
201
-
202
- end
203
-
204
- describe 'custom state method' do
205
-
206
- it "persists transitions" do
207
- ticket = Ticket.create!
208
- ticket.should be_open
209
- ticket.close!.should == true
210
- ticket.should be_closed
211
- end
212
-
213
- it "persists transitions with !" do
214
- ticket = Ticket.create!
215
- ticket.should be_open
216
- ticket.close!
217
- ticket.should be_closed
218
- end
219
-
220
- end
221
-
222
- end
223
-
@@ -1,195 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- describe SimpleStateMachine::Decorator do
4
-
5
- context "given a class" do
6
- before do
7
- klass = Class.new do
8
- # TODO remove the need for defining this method
9
- def self.state_machine_definition
10
- @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
11
- end
12
- end
13
- decorator = SimpleStateMachine::Decorator.new klass
14
- decorator.decorate SimpleStateMachine::Transition.new(:event, :state1, :state2)
15
- @instance = klass.new
16
- @instance.state = 'state1'
17
- end
18
-
19
- describe "#initialize" do
20
- it "defines a state_machine method" do
21
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
22
- end
23
-
24
- it "defines a state getter method" do
25
- @instance.should respond_to(:state)
26
- end
27
-
28
- it "defines a state setter method" do
29
- @instance.should respond_to(:state=)
30
- end
31
- end
32
-
33
- describe "#decorate" do
34
- it "defines state_helper_methods for both states" do
35
- @instance.state1?.should == true
36
- @instance.state2?.should == false
37
- end
38
-
39
- it "defines an event method" do
40
- @instance.should respond_to(:event)
41
- end
42
- end
43
- end
44
-
45
- context "given a class with predefined public methods" do
46
- before do
47
- klass = Class.new do
48
- # TODO remove the need for defining this method
49
- def self.state_machine_definition
50
- @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
51
- end
52
- # predefined methods
53
- def state1?() "state1" end
54
- def state2?() "state2" end
55
- def event() "predefined method" end
56
- end
57
- transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
58
- decorator = SimpleStateMachine::Decorator.new klass
59
- decorator.decorate transition
60
- klass.state_machine_definition.transitions << transition
61
- @instance = klass.new
62
- @instance.state = 'state1'
63
- end
64
-
65
- describe "#initialize" do
66
- it "defines a state_machine method" do
67
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
68
- end
69
-
70
- it "defines a state getter method" do
71
- @instance.should respond_to(:state)
72
- end
73
-
74
- it "defines a state setter method" do
75
- @instance.should respond_to(:state=)
76
- end
77
- end
78
-
79
- describe "#decorate" do
80
- it "does not overwrite predefined state_helper_methods" do
81
- @instance.state1?.should == "state1"
82
- @instance.state2?.should == "state2"
83
- end
84
-
85
- it "does not overwrite predefined event method" do
86
- @instance.event.should == "predefined method"
87
- end
88
- end
89
- end
90
-
91
- context "given a class with predefined protected methods" do
92
- before do
93
- klass = Class.new do
94
- # TODO remove the need for defining this method
95
- def self.state_machine_definition
96
- @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
97
- end
98
- # predefined methods
99
- protected
100
- def state1?() "state1" end
101
- def state2?() "state2" end
102
- def event() "predefined method" end
103
- end
104
- transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
105
- decorator = SimpleStateMachine::Decorator.new klass
106
- decorator.decorate transition
107
- klass.state_machine_definition.transitions << transition
108
- @instance = klass.new
109
- @instance.state = 'state1'
110
- end
111
-
112
- describe "#initialize" do
113
- it "defines a state_machine method" do
114
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
115
- end
116
-
117
- it "defines a state getter method" do
118
- @instance.should respond_to(:state)
119
- end
120
-
121
- it "defines a state setter method" do
122
- @instance.should respond_to(:state=)
123
- end
124
- end
125
-
126
- describe "#decorate" do
127
- it "does not overwrite predefined protected state_helper_methods" do
128
- @instance.send(:state1?).should == "state1"
129
- @instance.send(:state2?).should == "state2"
130
- end
131
-
132
- it "keeps predefined protected state_helper_methods protected" do
133
- expect { @instance.state1? }.to raise_error(NoMethodError)
134
- expect { @instance.state2? }.to raise_error(NoMethodError)
135
- end
136
-
137
- it "does not overwrite predefined protected event method" do
138
- @instance.event.should == "predefined method"
139
- end
140
- end
141
- end
142
-
143
- context "given a class with predefined private methods" do
144
- before do
145
- klass = Class.new do
146
- # TODO the need for defining this method
147
- def self.state_machine_definition
148
- @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
149
- end
150
- # predefined methods
151
- private
152
- def state1?() "state1" end
153
- def state2?() "state2" end
154
- def event() "predefined method" end
155
- end
156
- transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
157
- decorator = SimpleStateMachine::Decorator.new klass
158
- decorator.decorate transition
159
- klass.state_machine_definition.transitions << transition
160
- @instance = klass.new
161
- @instance.state = 'state1'
162
- end
163
-
164
- describe "#initialize" do
165
- it "defines a state_machine method" do
166
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
167
- end
168
-
169
- it "defines a state getter method" do
170
- @instance.should respond_to(:state)
171
- end
172
-
173
- it "defines a state setter method" do
174
- @instance.should respond_to(:state=)
175
- end
176
- end
177
-
178
- describe "#decorate" do
179
- it "does not overwrite predefined private state_helper_methods" do
180
- @instance.send(:state1?).should == "state1"
181
- @instance.send(:state2?).should == "state2"
182
- end
183
-
184
- it "keeps predefined private state_helper_methods private" do
185
- expect { @instance.state1? }.to raise_error(NoMethodError)
186
- expect { @instance.state2? }.to raise_error(NoMethodError)
187
- end
188
-
189
- it "does not overwrite predefined protected event method" do
190
- @instance.event.should == "predefined method"
191
- end
192
- end
193
- end
194
-
195
- end
@@ -1,60 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- describe "Examples" do
4
- describe "TrafficLight" do
5
- it "changes to the next state" do
6
- tl = TrafficLight.new
7
- tl.should be_green
8
- tl.change_state
9
- tl.should be_orange
10
- tl.change_state
11
- tl.should be_red
12
- tl.change_state
13
- tl.should be_green
14
- end
15
- end
16
-
17
- describe "Lamp" do
18
- it "changes between :on and :off" do
19
- lamp = Lamp.new
20
- lamp.should be_off
21
- lamp.push_button1
22
- lamp.should be_on
23
- lamp.push_button2
24
- lamp.should be_off
25
- lamp.push_button2
26
- lamp.should be_on
27
- lamp.push_button1
28
- lamp.should be_off
29
- end
30
- end
31
-
32
- describe "Conversation" do
33
- it "is :unread by default" do
34
- conversation = Conversation.new
35
- conversation.should be_unread
36
- end
37
-
38
- it "changes to read on view" do
39
- conversation = Conversation.new
40
- conversation.view
41
- conversation.should be_read
42
- end
43
-
44
- it "changes to closed on close" do
45
- conversation = Conversation.new
46
- conversation.close
47
- conversation.should be_closed
48
- end
49
-
50
- it "changes to closed on close if :read" do
51
- conversation = Conversation.new
52
- conversation.view
53
- conversation.close
54
- conversation.should be_closed
55
- end
56
-
57
- end
58
-
59
- end
60
-
@@ -1,24 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- describe "Mountable" do
4
- before do
5
- mountable_class = Class.new(SimpleStateMachine::StateMachineDefinition) do
6
- def initialize(subject)
7
- self.lazy_decorator = lambda { SimpleStateMachine::Decorator.new(subject) }
8
- add_transition(:event, :state1, :state2)
9
- end
10
- end
11
- klass = Class.new do
12
- extend SimpleStateMachine::Mountable
13
- self.state_machine_definition = mountable_class.new self
14
- end
15
- @instance = klass.new
16
- @instance.state = 'state1'
17
- end
18
-
19
- it "has state_helper methods" do
20
- @instance.should be_state1
21
- @instance.should_not be_state2
22
- end
23
-
24
- end
@@ -1,129 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
- require 'cgi'
3
-
4
- describe SimpleStateMachine do
5
-
6
- it "has an error that extends RuntimeError" do
7
- SimpleStateMachine::IllegalStateTransitionError.superclass.should == RuntimeError
8
- end
9
-
10
- describe ".event" do
11
-
12
- before do
13
- @klass = Class.new do
14
- extend SimpleStateMachine
15
- def initialize(state = 'state1')
16
- @state = state
17
- end
18
- end
19
- end
20
-
21
- it "changes state if event has multiple transitions" do
22
- klass = Class.new(@klass)
23
- klass.instance_eval do
24
- event :event, :state1 => :state2, :state2 => :state3
25
- end
26
- example = klass.new
27
- example.should be_state1
28
- example.event
29
- example.should be_state2
30
- example.event
31
- example.should be_state3
32
- end
33
-
34
- it "changes state if event has multiple froms" do
35
- klass = Class.new(@klass)
36
- klass.instance_eval do
37
- event :event, [:state1, :state2] => :state3
38
- end
39
- example = klass.new
40
- example.event
41
- example.should be_state3
42
- example = klass.new 'state2'
43
- example.should be_state2
44
- example.event
45
- example.should be_state3
46
- end
47
-
48
- it "changes state if event has all as from" do
49
- klass = Class.new(@klass)
50
- klass.instance_eval do
51
- event :other_event, :state1 => :state2
52
- event :event, :all => :state3
53
- end
54
- example = klass.new
55
- example.event
56
- example.should be_state3
57
- example = klass.new 'state2'
58
- example.should be_state2
59
- example.event
60
- example.should be_state3
61
- end
62
-
63
- it "changes state when state is a symbol instead of a string" do
64
- klass = Class.new(@klass)
65
- klass.instance_eval do
66
- event :event, :state1 => :state2
67
- end
68
- example = klass.new :state1
69
- example.state.should == :state1
70
- example.send(:event)
71
- example.should be_state2
72
- end
73
-
74
- it "changes state to error_state when error can be caught" do
75
- class_with_error = Class.new(@klass)
76
- class_with_error.instance_eval do
77
- define_method :raise_error do
78
- raise RuntimeError.new
79
- end
80
- event :raise_error, :state1 => :state2, RuntimeError => :failed
81
- end
82
- example = class_with_error.new
83
- example.should be_state1
84
- example.raise_error
85
- example.should be_failed
86
- end
87
-
88
- it "raises an error if an invalid state_transition is called" do
89
- klass = Class.new(@klass)
90
- klass.instance_eval do
91
- event :event, :state1 => :state2
92
- event :event2, :state2 => :state3
93
- end
94
- example = klass.new
95
- lambda { example.event2 }.should raise_error(SimpleStateMachine::IllegalStateTransitionError, "You cannot 'event2' when state is 'state1'")
96
- end
97
-
98
- it "returns what the decorated method returns" do
99
- klass = Class.new(@klass)
100
- klass.instance_eval do
101
- event :event1, :state1 => :state2
102
- define_method :event2 do
103
- 'event2'
104
- end
105
- event :event2, :state2 => :state3
106
- end
107
- example = klass.new
108
- example.event1.should == nil
109
- example.event2.should == 'event2'
110
- end
111
-
112
- it "calls existing methods" do
113
- klass = Class.new(@klass)
114
- klass.instance_eval do
115
- attr_accessor :event_called
116
- define_method :event do
117
- @event_called = true
118
- end
119
- event :event, :state1 => :state2
120
- end
121
- example = klass.new
122
- example.event
123
- example.event_called.should == true
124
- end
125
-
126
- end
127
-
128
- end
129
-
data/spec/spec_helper.rb DELETED
@@ -1,7 +0,0 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
- require 'simple_state_machine'
4
- require 'examples/conversation'
5
- require 'examples/lamp'
6
- require 'examples/traffic_light'
7
-