stateflow 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest CHANGED
@@ -7,9 +7,15 @@ examples/test.rb
7
7
  init.rb
8
8
  lib/stateflow.rb
9
9
  lib/stateflow/event.rb
10
+ lib/stateflow/exception.rb
10
11
  lib/stateflow/machine.rb
11
12
  lib/stateflow/persistence.rb
12
13
  lib/stateflow/persistence/active_record.rb
13
14
  lib/stateflow/persistence/mongo_mapper.rb
15
+ lib/stateflow/persistence/mongoid.rb
16
+ lib/stateflow/persistence/none.rb
14
17
  lib/stateflow/state.rb
15
18
  lib/stateflow/transition.rb
19
+ spec/spec_helper.rb
20
+ spec/stateflow_spec.rb
21
+ stateflow.gemspec
data/README.rdoc CHANGED
@@ -3,9 +3,9 @@
3
3
  == TODO
4
4
 
5
5
  * More Persistence layers
6
- * Tests
6
+ * More Tests
7
7
 
8
- This is the basics of the gem. THIS IS NOT PRODUCTION READY UNTIL TESTS ARE DONE. Please check out the examples directory for usage until this README gets fleshed out. Feel free to fork and modify as you please.
8
+ This is the basics of the gem. Please check out the examples directory or tests for usage until this README gets fleshed out. Feel free to fork and modify as you please.
9
9
 
10
10
  == INSTALL
11
11
 
@@ -13,14 +13,16 @@ This is the basics of the gem. THIS IS NOT PRODUCTION READY UNTIL TESTS ARE DONE
13
13
 
14
14
  == Usage
15
15
 
16
- As you can see below, Stateflow's API is very similar to AASM, but allows for a more dynamic state transition flow. Stateflow supports persistence/storage with MongoMapper, and ActiveRecord. Request any others or push them to me.
16
+ As you can see below, Stateflow's API is very similar to AASM, but allows for a more dynamic state transition flow. Stateflow supports persistence/storage with Mongoid, MongoMapper, and ActiveRecord. Request any others or push them to me.
17
17
 
18
18
  Stateflow defaults to ActiveRecord but you can set the persistence layer with:
19
19
 
20
20
  Stateflow.persistence = :mongo_mapper
21
21
  OR
22
22
  Stateflow.persistence = :active_record
23
-
23
+ OR
24
+ Stateflow.persistence = :mongoid
25
+
24
26
  Stateflow allows dynamic :to transitions with :decide. The result :decide returns needs to be one of the states listed in the :to array, otherwise it wont allow the transition. Please view the advanced example below for usage.
25
27
 
26
28
  You can set the default column with the state_column function in the stateflow block. The default state column is "state".
@@ -29,10 +31,11 @@ You can set the default column with the state_column function in the stateflow b
29
31
 
30
32
  == Basic Example
31
33
 
32
- Please note these examples need a persistence layer to operate. I might allow non persistent stateflows in the future.
33
-
34
34
  require 'rubygems'
35
35
  require 'stateflow'
36
+
37
+ # No persistence
38
+ Stateflow.persistence :none
36
39
 
37
40
  class Robot
38
41
  include Stateflow
@@ -54,6 +57,9 @@ Please note these examples need a persistence layer to operate. I might allow no
54
57
 
55
58
  require 'rubygems'
56
59
  require 'stateflow'
60
+
61
+ # No persistence
62
+ Stateflow.persistence :none
57
63
 
58
64
  class Test
59
65
  include Stateflow
@@ -89,7 +95,7 @@ Please note these examples need a persistence layer to operate. I might allow no
89
95
  end
90
96
 
91
97
  def likes_ice_cream?
92
- rand(10) > 5 ? :mixeds : :hate
98
+ rand(10) > 5 ? :mixed : :hate
93
99
  end
94
100
 
95
101
  def exit_love
data/Rakefile CHANGED
@@ -2,11 +2,11 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('stateflow', '0.0.4') do |p|
5
+ Echoe.new('stateflow', '0.1.0') do |p|
6
6
  p.description = "State machine that allows dynamic transitions for business workflows"
7
- p.url = "http://github.com/ryanza/stateflow"
7
+ p.url = "http://github.com/platform45/stateflow"
8
8
  p.author = "Ryan Oberholzer"
9
9
  p.email = "ryan@platform45.com"
10
10
  p.ignore_pattern = ["tmp/*", "script/*"]
11
- p.development_dependencies = []
11
+ p.development_dependencies = ["rspec"]
12
12
  end
data/lib/stateflow.rb CHANGED
@@ -49,9 +49,9 @@ module Stateflow
49
49
  end
50
50
 
51
51
  private
52
- def fire_event(event)
53
- event = machine.events[event.to_sym]
54
- raise Exception.new("No event matches #{event}") if event.nil?
52
+ def fire_event(event_name)
53
+ event = machine.events[event_name.to_sym]
54
+ raise Stateflow::NoEventFound.new("No event matches #{event_name}") if event.nil?
55
55
  event.fire(current_state, self)
56
56
  end
57
57
  end
@@ -61,4 +61,5 @@ module Stateflow
61
61
  autoload :Event, 'stateflow/event'
62
62
  autoload :Transition, 'stateflow/transition'
63
63
  autoload :Persistence, 'stateflow/persistence'
64
+ autoload :Exception, 'stateflow/exception'
64
65
  end
@@ -1,7 +1,4 @@
1
1
  module Stateflow
2
- class NoTransitionFound < Exception; end
3
- class NoStateFound < Exception; end
4
-
5
2
  class Event
6
3
  attr_accessor :name, :transitions
7
4
 
@@ -0,0 +1,5 @@
1
+ module Stateflow
2
+ class NoTransitionFound < Exception; end
3
+ class NoStateFound < Exception; end
4
+ class NoEventFound < Exception; end
5
+ end
@@ -6,10 +6,16 @@ module Stateflow
6
6
  Stateflow::Persistence::MongoMapper.install(base)
7
7
  when :active_record
8
8
  Stateflow::Persistence::ActiveRecord.install(base)
9
+ when :mongoid
10
+ Stateflow::Persistence::Mongoid.install(base)
11
+ when :none
12
+ Stateflow::Persistence::None.install(base)
9
13
  end
10
14
  end
11
15
 
12
16
  autoload :MongoMapper, 'stateflow/persistence/mongo_mapper'
13
17
  autoload :ActiveRecord, 'stateflow/persistence/active_record'
18
+ autoload :Mongoid, 'stateflow/persistence/mongoid'
19
+ autoload :None, 'stateflow/persistence/none'
14
20
  end
15
21
  end
@@ -0,0 +1,24 @@
1
+ module Stateflow
2
+ module Persistence
3
+ module Mongoid
4
+ def self.install(base)
5
+ base.respond_to?(:before_validation_on_create) ? base.before_validation_on_create(:ensure_initial_state) : base.before_validation(:ensure_initial_state, :on => :create)
6
+ base.send :include, InstanceMethods
7
+ end
8
+
9
+ module InstanceMethods
10
+ def load_from_persistence
11
+ self.send machine.state_column.to_sym
12
+ end
13
+
14
+ def save_to_persistence(new_state)
15
+ self.update_attributes(machine.state_column.to_sym => new_state)
16
+ end
17
+
18
+ def ensure_initial_state
19
+ send("#{self.machine.state_column.to_s}=", self.current_state.name.to_s) if send(self.machine.state_column.to_s).blank?
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ module Stateflow
2
+ module Persistence
3
+ module None
4
+ def self.install(base)
5
+ base.send :include, InstanceMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ attr_accessor :state
10
+
11
+ def load_from_persistence
12
+ @state
13
+ end
14
+
15
+ def save_to_persistence(new_state)
16
+ @state = new_state
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'stateflow'
5
+
6
+ require 'spec'
7
+ require 'spec/autorun'
8
+
9
+ Stateflow.persistence = :none
10
+
11
+ Spec::Runner.configure do |config|
12
+ end
@@ -0,0 +1,277 @@
1
+ require 'spec_helper'
2
+
3
+ class Robot
4
+ include Stateflow
5
+
6
+ stateflow do
7
+ initial :green
8
+
9
+ state :green, :yellow, :red
10
+
11
+ event :change_color do
12
+ transitions :from => :green, :to => :yellow
13
+ transitions :from => :yellow, :to => :red
14
+ transitions :from => :red, :to => :green
15
+ end
16
+ end
17
+ end
18
+
19
+ class Car
20
+ include Stateflow
21
+
22
+ stateflow do
23
+ initial :parked
24
+
25
+ state :parked do
26
+ enter do
27
+ "Entering parked"
28
+ end
29
+
30
+ exit do
31
+ "Exiting parked"
32
+ end
33
+ end
34
+
35
+ state :driving do
36
+ enter do
37
+ "Entering parked"
38
+ end
39
+ end
40
+
41
+ event :drive do
42
+ transitions :from => :parked, :to => :driving
43
+ end
44
+
45
+ event :park do
46
+ transitions :from => :driving, :to => :parked
47
+ end
48
+ end
49
+ end
50
+
51
+ class Bob
52
+ include Stateflow
53
+
54
+ stateflow do
55
+ state :yellow, :red, :purple
56
+
57
+ event :change_hair_color do
58
+ transitions :from => :purple, :to => :yellow
59
+ transitions :from => :yellow, :to => :red
60
+ transitions :from => :red, :to => :purple
61
+ end
62
+ end
63
+ end
64
+
65
+ class Dater
66
+ include Stateflow
67
+
68
+ stateflow do
69
+ state :single, :dating, :married
70
+
71
+ event :take_out do
72
+ transitions :from => :single, :to => :dating
73
+ end
74
+
75
+ event :gift do
76
+ transitions :from => :dating, :to => [:single, :married], :decide => :girls_mood?
77
+ end
78
+
79
+ event :blank_decision do
80
+ transitions :from => :single, :to => [:single, :married], :decide => :girls_mood?
81
+ end
82
+
83
+ event :fail do
84
+ transitions :from => :dating, :to => [:single, :married]
85
+ end
86
+ end
87
+
88
+ def girls_mood?
89
+ end
90
+ end
91
+
92
+ describe Stateflow do
93
+ describe "class methods" do
94
+ it "should respond to stateflow block to setup the intial stateflow" do
95
+ Robot.should respond_to(:stateflow)
96
+ end
97
+
98
+ it "should respond to the machine attr accessor" do
99
+ Robot.should respond_to(:machine)
100
+ end
101
+ end
102
+
103
+ describe "instance methods" do
104
+ before(:each) do
105
+ @r = Robot.new
106
+ end
107
+
108
+ it "should respond to current state" do
109
+ @r.should respond_to(:current_state)
110
+ end
111
+
112
+ it "should respond to the current state setter" do
113
+ @r.should respond_to(:current_state=)
114
+ end
115
+
116
+ it "should respond to the current machine" do
117
+ @r.should respond_to(:machine)
118
+ end
119
+
120
+ it "should respond to load from persistence" do
121
+ @r.should respond_to(:load_from_persistence)
122
+ end
123
+
124
+ it "should respond to save to persistence" do
125
+ @r.should respond_to(:save_to_persistence)
126
+ end
127
+ end
128
+
129
+ describe "initial state" do
130
+ it "should set the initial state" do
131
+ robot = Robot.new
132
+ robot.current_state.name.should == :green
133
+ end
134
+
135
+ it "should return true for green?" do
136
+ robot = Robot.new
137
+ robot.green?.should be_true
138
+ end
139
+
140
+ it "should return false for yellow?" do
141
+ robot = Robot.new
142
+ robot.yellow?.should be_false
143
+ end
144
+
145
+ it "should return false for red?" do
146
+ robot = Robot.new
147
+ robot.red?.should be_false
148
+ end
149
+
150
+ it "should set the initial state to the first state set" do
151
+ bob = Bob.new
152
+ bob.current_state.name.should == :yellow
153
+ bob.yellow?.should be_true
154
+ end
155
+ end
156
+
157
+ it "robot class should contain red, yellow and green states" do
158
+ robot = Robot.new
159
+ robot.machine.states.keys.should include(:red, :yellow, :green)
160
+ end
161
+
162
+ describe "firing events" do
163
+ it "should call the fire method on event" do
164
+ robot = Robot.new
165
+ robot.machine.events[:change_color].should_receive(:fire)
166
+ robot.change_color!
167
+ end
168
+
169
+ it "should call the fire_event method" do
170
+ robot = Robot.new
171
+ robot.should_receive(:fire_event).with(:change_color)
172
+ robot.change_color!
173
+ end
174
+
175
+ it "should raise an exception if the event does not exist" do
176
+ robot = Robot.new
177
+ lambda { robot.send(:fire_event, :fake) }.should raise_error(Stateflow::NoEventFound)
178
+ end
179
+
180
+ it "should fire the event" do
181
+ robot = Robot.new
182
+ robot.should_receive(:fire_event).with(:change_color)
183
+ robot.change_color!
184
+ end
185
+
186
+ it "should update the event" do
187
+ robot = Robot.new
188
+ robot.current_state.name.should == :green
189
+ robot.change_color!
190
+ robot.current_state.name.should == :yellow
191
+ end
192
+
193
+ describe "before filters" do
194
+ before(:each) do
195
+ @car = Car.new
196
+ end
197
+
198
+ it "should call the exit state before filter on the exiting old state" do
199
+ @car.machine.states[:parked].should_receive(:execute_action).with(:exit, @car)
200
+ @car.drive!
201
+ end
202
+
203
+ it "should call the enter state before filter on the entering new state" do
204
+ @car.machine.states[:driving].should_receive(:execute_action).with(:enter, @car)
205
+ @car.drive!
206
+ end
207
+ end
208
+ end
209
+
210
+ describe "persistence" do
211
+ it "should attempt to persist the new state and the name should be a string" do
212
+ robot = Robot.new
213
+ robot.should_receive(:save_to_persistence).with("yellow")
214
+ robot.change_color!
215
+ end
216
+
217
+ it "should attempt to read the initial state from the persistence" do
218
+ robot = Robot.new
219
+
220
+ def robot.load_from_persistence
221
+ :red
222
+ end
223
+
224
+ robot.current_state.name.should == :red
225
+ end
226
+ end
227
+
228
+ describe "dynamic to transitions" do
229
+ it "should raise an error without any decide argument" do
230
+ date = Dater.new
231
+
232
+ def date.load_from_persistence
233
+ :dating
234
+ end
235
+
236
+ lambda { date.fail! }.should raise_error(Stateflow::IncorrectTransition)
237
+ end
238
+
239
+ it "should raise an error if the decision method does not return a valid state" do
240
+ date = Dater.new
241
+
242
+ def date.girls_mood?
243
+ :lol
244
+ end
245
+
246
+ lambda { date.blank_decision! }.should raise_error(Stateflow::NoStateFound, "Decision did not return a state that was set in the 'to' argument")
247
+ end
248
+
249
+ it "should raise an error if the decision method returns blank/nil" do
250
+ date = Dater.new
251
+
252
+ def date.girls_mood?
253
+ end
254
+
255
+ lambda { date.blank_decision! }.should raise_error(Stateflow::NoStateFound, "Decision did not return a state that was set in the 'to' argument")
256
+ end
257
+
258
+ it "should calculate the decide block or method and transition to the correct state" do
259
+ date = Dater.new
260
+
261
+ def date.load_from_persistence
262
+ :dating
263
+ end
264
+
265
+ date.current_state.name.should == :dating
266
+
267
+ def date.girls_mood?
268
+ :single
269
+ end
270
+
271
+ date.gift!
272
+
273
+ date.current_state.name.should == :single
274
+ end
275
+ end
276
+ end
277
+
data/stateflow.gemspec CHANGED
@@ -2,20 +2,20 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{stateflow}
5
- s.version = "0.0.4"
5
+ s.version = "0.1.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Ryan Oberholzer"]
9
- s.date = %q{2010-04-15}
9
+ s.date = %q{2010-05-04}
10
10
  s.description = %q{State machine that allows dynamic transitions for business workflows}
11
11
  s.email = %q{ryan@platform45.com}
12
- s.extra_rdoc_files = ["README.rdoc", "lib/stateflow.rb", "lib/stateflow/event.rb", "lib/stateflow/machine.rb", "lib/stateflow/persistence.rb", "lib/stateflow/persistence/active_record.rb", "lib/stateflow/persistence/mongo_mapper.rb", "lib/stateflow/state.rb", "lib/stateflow/transition.rb"]
13
- s.files = ["LICENCE", "Manifest", "README.rdoc", "Rakefile", "examples/robot.rb", "examples/test.rb", "init.rb", "lib/stateflow.rb", "lib/stateflow/event.rb", "lib/stateflow/machine.rb", "lib/stateflow/persistence.rb", "lib/stateflow/persistence/active_record.rb", "lib/stateflow/persistence/mongo_mapper.rb", "lib/stateflow/state.rb", "lib/stateflow/transition.rb", "stateflow.gemspec"]
14
- s.homepage = %q{http://github.com/ryanza/stateflow}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/stateflow.rb", "lib/stateflow/event.rb", "lib/stateflow/exception.rb", "lib/stateflow/machine.rb", "lib/stateflow/persistence.rb", "lib/stateflow/persistence/active_record.rb", "lib/stateflow/persistence/mongo_mapper.rb", "lib/stateflow/persistence/mongoid.rb", "lib/stateflow/persistence/none.rb", "lib/stateflow/state.rb", "lib/stateflow/transition.rb"]
13
+ s.files = ["LICENCE", "Manifest", "README.rdoc", "Rakefile", "examples/robot.rb", "examples/test.rb", "init.rb", "lib/stateflow.rb", "lib/stateflow/event.rb", "lib/stateflow/exception.rb", "lib/stateflow/machine.rb", "lib/stateflow/persistence.rb", "lib/stateflow/persistence/active_record.rb", "lib/stateflow/persistence/mongo_mapper.rb", "lib/stateflow/persistence/mongoid.rb", "lib/stateflow/persistence/none.rb", "lib/stateflow/state.rb", "lib/stateflow/transition.rb", "spec/spec_helper.rb", "spec/stateflow_spec.rb", "stateflow.gemspec"]
14
+ s.homepage = %q{http://github.com/platform45/stateflow}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Stateflow", "--main", "README.rdoc"]
16
16
  s.require_paths = ["lib"]
17
17
  s.rubyforge_project = %q{stateflow}
18
- s.rubygems_version = %q{1.3.5}
18
+ s.rubygems_version = %q{1.3.6}
19
19
  s.summary = %q{State machine that allows dynamic transitions for business workflows}
20
20
 
21
21
  if s.respond_to? :specification_version then
@@ -23,8 +23,11 @@ Gem::Specification.new do |s|
23
23
  s.specification_version = 3
24
24
 
25
25
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ s.add_development_dependency(%q<rspec>, [">= 0"])
26
27
  else
28
+ s.add_dependency(%q<rspec>, [">= 0"])
27
29
  end
28
30
  else
31
+ s.add_dependency(%q<rspec>, [">= 0"])
29
32
  end
30
33
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stateflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Ryan Oberholzer
@@ -9,10 +14,21 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-04-15 00:00:00 +02:00
17
+ date: 2010-05-04 00:00:00 +02:00
13
18
  default_executable:
14
- dependencies: []
15
-
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
16
32
  description: State machine that allows dynamic transitions for business workflows
17
33
  email: ryan@platform45.com
18
34
  executables: []
@@ -23,10 +39,13 @@ extra_rdoc_files:
23
39
  - README.rdoc
24
40
  - lib/stateflow.rb
25
41
  - lib/stateflow/event.rb
42
+ - lib/stateflow/exception.rb
26
43
  - lib/stateflow/machine.rb
27
44
  - lib/stateflow/persistence.rb
28
45
  - lib/stateflow/persistence/active_record.rb
29
46
  - lib/stateflow/persistence/mongo_mapper.rb
47
+ - lib/stateflow/persistence/mongoid.rb
48
+ - lib/stateflow/persistence/none.rb
30
49
  - lib/stateflow/state.rb
31
50
  - lib/stateflow/transition.rb
32
51
  files:
@@ -39,15 +58,20 @@ files:
39
58
  - init.rb
40
59
  - lib/stateflow.rb
41
60
  - lib/stateflow/event.rb
61
+ - lib/stateflow/exception.rb
42
62
  - lib/stateflow/machine.rb
43
63
  - lib/stateflow/persistence.rb
44
64
  - lib/stateflow/persistence/active_record.rb
45
65
  - lib/stateflow/persistence/mongo_mapper.rb
66
+ - lib/stateflow/persistence/mongoid.rb
67
+ - lib/stateflow/persistence/none.rb
46
68
  - lib/stateflow/state.rb
47
69
  - lib/stateflow/transition.rb
70
+ - spec/spec_helper.rb
71
+ - spec/stateflow_spec.rb
48
72
  - stateflow.gemspec
49
73
  has_rdoc: true
50
- homepage: http://github.com/ryanza/stateflow
74
+ homepage: http://github.com/platform45/stateflow
51
75
  licenses: []
52
76
 
53
77
  post_install_message:
@@ -64,18 +88,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
64
88
  requirements:
65
89
  - - ">="
66
90
  - !ruby/object:Gem::Version
91
+ segments:
92
+ - 0
67
93
  version: "0"
68
- version:
69
94
  required_rubygems_version: !ruby/object:Gem::Requirement
70
95
  requirements:
71
96
  - - ">="
72
97
  - !ruby/object:Gem::Version
98
+ segments:
99
+ - 1
100
+ - 2
73
101
  version: "1.2"
74
- version:
75
102
  requirements: []
76
103
 
77
104
  rubyforge_project: stateflow
78
- rubygems_version: 1.3.5
105
+ rubygems_version: 1.3.6
79
106
  signing_key:
80
107
  specification_version: 3
81
108
  summary: State machine that allows dynamic transitions for business workflows