edge-state-machine 0.0.2 → 0.0.3

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.
@@ -1,99 +1,5 @@
1
1
  require 'mongoid/mongoid_helper'
2
2
 
3
- class MongoTrafficLight
4
- include Mongoid::Document
5
- include Mongoid::EdgeStateMachine
6
- field :state
7
-
8
- state_machine do
9
- state :off
10
-
11
- state :red
12
- state :green
13
- state :yellow
14
-
15
- event :red_on do
16
- transitions :to => :red, :from => [:yellow]
17
- end
18
-
19
- event :green_on do
20
- transitions :to => :green, :from => [:red]
21
- end
22
-
23
- event :yellow_on do
24
- transitions :to => :yellow, :from => [:green]
25
- end
26
-
27
- event :reset do
28
- transitions :to => :red, :from => [:off]
29
- end
30
- end
31
- end
32
-
33
- class MongoProtectedTrafficLight < MongoTrafficLight
34
- attr_protected :state
35
- end
36
-
37
- class MongoValidatingTrafficLight < MongoTrafficLight
38
- validate {|t| errors.add(:base, 'This TrafficLight will never validate after creation') unless t.new_record? }
39
- end
40
-
41
- class MongoConditionalValidatingTrafficLight < MongoTrafficLight
42
- validates :name, :presence => true, :length => { :within => 20..40 }, :confirmation => true, :if => :red?
43
- end
44
-
45
- class MongoOrder
46
- include Mongoid::Document
47
- include Mongoid::EdgeStateMachine
48
-
49
- field :state, :type => String
50
- field :order_number, :type => Integer
51
- field :paid_at, :type => DateTime
52
- field :prepared_on, :type => DateTime
53
- field :dispatched_at, :type => DateTime
54
- field :cancellation_date, :type => Date
55
-
56
- state_machine do
57
- state :opened
58
- state :placed
59
- state :paid
60
- state :prepared
61
- state :delivered
62
- state :cancelled
63
-
64
- # no timestamp col is being specified here - should be ignored
65
- event :place do
66
- transitions :from => :opened, :to => :placed
67
- end
68
-
69
- # should set paid_at timestamp
70
- event :pay, :timestamp => true do
71
- transitions :from => :placed, :to => :paid
72
- end
73
-
74
- # should set prepared_on
75
- event :prepare, :timestamp => true do
76
- transitions :from => :paid, :to => :prepared
77
- end
78
-
79
- # should set dispatched_at
80
- event :deliver, :timestamp => "dispatched_at" do
81
- transitions :from => :prepared, :to => :delivered
82
- end
83
-
84
- # should set cancellation_date
85
- event :cancel, :timestamp => :cancellation_date do
86
- transitions :from => [:placed, :paid, :prepared], :to => :cancelled
87
- end
88
-
89
- # should raise an exception as there is no timestamp col
90
- event :reopen, :timestamp => true do
91
- transitions :from => :cancelled, :to => :opened
92
- end
93
-
94
- end
95
- end
96
-
97
3
  describe "mongoid state machine" do
98
4
 
99
5
  context "existing mongo document" do
@@ -121,7 +27,7 @@ describe "mongoid state machine" do
121
27
  @light.reset
122
28
  @light.current_state.should == :red
123
29
  @light.reload
124
- @light.state.should == "off"
30
+ @light.state.should == :off
125
31
  end
126
32
 
127
33
  it "should persists state on transition" do
@@ -131,8 +37,14 @@ describe "mongoid state machine" do
131
37
  @light.state.should == "red"
132
38
  end
133
39
 
40
+ it "should initialize the current state when loaded from database" do
41
+ @light.reset!
42
+ loaded_light = MongoTrafficLight.find(@light.id)
43
+ loaded_light.current_state.should == :red
44
+ end
45
+
134
46
  it "should raise error on transition to an invalid state" do
135
- expect { @light.yellow_on }.should raise_error EdgeStateMachine::InvalidTransition
47
+ expect { @light.yellow_on }.should raise_error EdgeStateMachine::NoTransitionFound
136
48
  @light.current_state.should == :off
137
49
  end
138
50
 
@@ -145,8 +57,8 @@ describe "mongoid state machine" do
145
57
  end
146
58
 
147
59
  it "should not validate when try transition with wrong state " do
148
- for s in @light.class.state_machine.states
149
- @light.state = s.name
60
+ for s in @light.class.state_machines[:default].states.keys
61
+ @light.state = s
150
62
  @light.valid?.should == true
151
63
  end
152
64
  @light.state = "invalid_one"
@@ -158,18 +70,37 @@ describe "mongoid state machine" do
158
70
  expect {validating_light.reset!}.should raise_error Mongoid::Errors::Validations
159
71
  end
160
72
 
161
- #it "should state query method used in a validation condition" do
162
- #validating_light = MongoConditionalValidatingTrafficLight.create!
73
+ it "should state query method used in a validation condition" do
74
+ validating_light = MongoConditionalValidatingTrafficLight.create!
163
75
  #expect {validating_light.reset!}.should raise_error Mongoid::RecordInvalid
164
- #validating_light.off?.should == true
165
- #end
76
+ validating_light.off?.should == true
77
+ end
166
78
 
167
79
  it "should reload the model when current state resets" do
168
80
  @light.reset
169
81
  @light.red?.should == true
170
82
  @light.update_attribute(:state, 'green')
171
- @light.reload.green?.should == false # reloaded state should come from instance variable not from database
172
- # because the state can be changed without persist
83
+ @light.reload.green?.should == true # reloaded state should come from database
84
+ end
85
+
86
+ describe "scopes" do
87
+ it "should be added for each state" do
88
+ MongoTrafficLight.should respond_to(:off)
89
+ MongoTrafficLight.should respond_to(:red)
90
+ end
91
+
92
+ it "should not be added for each state" do
93
+ #MongoTrafficLightNoScope.should_not respond_to(:off)
94
+ #MongoTrafficLightNoScope.should_not respond_to(:red)
95
+ end
96
+
97
+ it "should behave like scopes" do
98
+ 3.times { MongoTrafficLight.create(:state => "off") }
99
+ 3.times { MongoTrafficLight.create(:state => "red") }
100
+ # one was created before
101
+ MongoTrafficLight.off.count.should == 4
102
+ MongoTrafficLight.red.count.should == 3
103
+ end
173
104
  end
174
105
  end
175
106
 
@@ -198,54 +129,5 @@ describe "mongoid state machine" do
198
129
  expect { @order.place! }.should_not raise_error
199
130
  @order.state.should == "placed"
200
131
  end
201
-
202
- it "should set paid_at when moving to paid" do
203
- @order = create_order(:placed)
204
- @order.pay!
205
- @order.reload
206
- @order.paid_at.should_not be_nil
207
- end
208
-
209
- it "should set prepared_on when moving to prepared" do
210
- @order = create_order(:paid)
211
- @order.prepare!
212
- @order.reload
213
- @order.prepared_on.should_not be_nil
214
- end
215
-
216
- it "should set dispatched_at when moving to delivered" do
217
- @order = create_order(:prepared)
218
- @order.deliver!
219
- @order.reload
220
- @order.dispatched_at.should_not be_nil
221
- end
222
-
223
- it "should set cancellation_date when moving to cancelled" do
224
- @order = create_order(:placed)
225
- @order.cancel!
226
- @order.reload
227
- @order.cancellation_date.should_not be_nil
228
- end
229
-
230
- it "should raise an exception as there is no attribute when moving to reopened" do
231
- @order = create_order(:cancelled)
232
- expect { @order.re_open! }.should raise_error NoMethodError
233
- @order.reload
234
- end
235
-
236
- it "should raise an exception when passing an invalid value to timestamp options" do
237
- expect {
238
- class MongoOrder
239
- include Mongoid::Document
240
- include Mongoid::EdgeStateMachine
241
-
242
- state_machine do
243
- event :replace, timestamp: 1 do
244
- transitions :from => :prepared, :to => :placed
245
- end
246
- end
247
- end
248
- }.should raise_error ArgumentError
249
- end
250
132
  end
251
133
  end
@@ -0,0 +1,54 @@
1
+ require 'mongoid'
2
+ require 'mongoid/edge-state-machine'
3
+
4
+ class MongoOrder
5
+ include Mongoid::Document
6
+ include Mongoid::EdgeStateMachine
7
+
8
+ field :state, :type => String
9
+ field :order_number, :type => Integer
10
+ field :paid_at, :type => DateTime
11
+ field :prepared_on, :type => DateTime
12
+ field :dispatched_at, :type => DateTime
13
+ field :cancellation_date, :type => Date
14
+
15
+ state_machine do
16
+ state :opened
17
+ state :placed
18
+ state :paid
19
+ state :prepared
20
+ state :delivered
21
+ state :cancelled
22
+
23
+ # no timestamp col is being specified here - should be ignored
24
+ event :place do
25
+ transition :from => :opened, :to => :placed
26
+ end
27
+
28
+ # should set paid_at timestamp
29
+ event :pay do
30
+ transition :from => :placed, :to => :paid
31
+ end
32
+
33
+ # should set prepared_on
34
+ event :prepare do
35
+ transition :from => :paid, :to => :prepared
36
+ end
37
+
38
+ # should set dispatched_at
39
+ event :deliver do
40
+ transition :from => :prepared, :to => :delivered
41
+ end
42
+
43
+ # should set cancellation_date
44
+ event :cancel do
45
+ transition :from => [:placed, :paid, :prepared], :to => :cancelled
46
+ end
47
+
48
+ # should raise an exception as there is no timestamp col
49
+ event :reopen do
50
+ transition :from => :cancelled, :to => :opened
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ require 'mongoid'
2
+ require 'mongoid/edge-state-machine'
3
+
4
+ class MongoTrafficLight
5
+ include Mongoid::Document
6
+ include Mongoid::EdgeStateMachine
7
+ field :state
8
+
9
+ state_machine do
10
+ create_scopes true
11
+ persisted_to :state
12
+ state :off
13
+
14
+ state :red
15
+ state :green
16
+ state :yellow
17
+
18
+ event :red_on do
19
+ transition :to => :red, :from => [:yellow]
20
+ end
21
+
22
+ event :green_on do
23
+ transition :to => :green, :from => [:red]
24
+ end
25
+
26
+ event :yellow_on do
27
+ transition :to => :yellow, :from => [:green]
28
+ end
29
+
30
+ event :reset do
31
+ transition :to => :red, :from => [:off]
32
+ end
33
+ end
34
+ end
35
+
36
+ class MongoProtectedTrafficLight < MongoTrafficLight
37
+ attr_protected :state
38
+ end
39
+
40
+ class MongoValidatingTrafficLight < MongoTrafficLight
41
+ validate {|t| errors.add(:base, 'This TrafficLight will never validate after creation') unless t.new_record? }
42
+ end
43
+
44
+ class MongoConditionalValidatingTrafficLight < MongoTrafficLight
45
+ validates :name, :presence => true, :length => { :within => 20..40 }, :confirmation => true, :if => :red?
46
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+ require 'non_persistent/samples/dice'
3
+ require 'non_persistent/samples/user'
4
+ require 'non_persistent/samples/microwave'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+
9
+ require 'edge-state-machine'
@@ -0,0 +1,80 @@
1
+ require 'non_persistent/non_persistent_helper'
2
+
3
+
4
+ describe Dice do
5
+ before do
6
+ @dice = Dice.new
7
+ end
8
+
9
+ it "should have a current state equals with the initial state" do
10
+ @dice.current_state_name.should == :one
11
+ end
12
+
13
+ it "should have an event trigger method" do
14
+ @dice.should respond_to :roll
15
+ end
16
+
17
+ it "should change state after the event method is called" do
18
+ @dice.roll
19
+ @dice.current_state_name.should_not == :one
20
+ end
21
+ end
22
+
23
+
24
+ describe User do
25
+ before do
26
+ @user = User.new
27
+ end
28
+
29
+ it "should have a current state equals with the initial state" do
30
+ @user.current_state_name.should == :pending
31
+ end
32
+
33
+ it "should have an event trigger method" do
34
+ @user.should respond_to :activate
35
+ end
36
+
37
+ it "should have state verfification methods for each state" do
38
+ @user.pending?.should == true
39
+ @user.active?.should_not == true
40
+ @user.blocked?.should_not == true
41
+ end
42
+
43
+ it "should change state after the event method is called" do
44
+ @user.activate
45
+ @user.current_state_name.should == :active
46
+ end
47
+ end
48
+
49
+ describe Microwave do
50
+ before do
51
+ @microwave = Microwave.new
52
+ end
53
+
54
+ it "should have a current state equals with the initial state" do
55
+ @microwave.current_state_name(:microwave).should == :unplugged
56
+ end
57
+
58
+ it "should have an event trigger method" do
59
+ @microwave.should respond_to :plug_in
60
+ end
61
+
62
+ it "should have state verfification methods for each state" do
63
+ @microwave.unplugged?.should == true
64
+ @microwave.plugged?.should_not == true
65
+ @microwave.door_opened?.should_not == true
66
+ @microwave.door_closed?.should_not == true
67
+ @microwave.started_in_grill_mode?.should_not == true
68
+ @microwave.started?.should_not == true
69
+ end
70
+
71
+ it "should change state after the event method is called" do
72
+ @microwave.plug_in
73
+ @microwave.current_state_name(:microwave).should == :plugged
74
+ @microwave.plugged?.should == true
75
+ end
76
+
77
+ it "should execute enter state action" do
78
+ @microwave.plug_in
79
+ end
80
+ end
@@ -0,0 +1,31 @@
1
+ require 'edge-state-machine'
2
+
3
+ class Dice
4
+ include EdgeStateMachine
5
+
6
+ state_machine do
7
+ state :one
8
+ state :two
9
+ state :three
10
+ state :four
11
+ state :five
12
+ state :six
13
+
14
+ event :roll do
15
+ transition :from => [:one, :two, :three, :four, :five, :six],
16
+ :to => [:one, :two, :three, :four, :five, :six],
17
+ :guard => :roll_result,
18
+ :on_transition => :display_dice_rolling_animation
19
+ end
20
+ end
21
+
22
+ def roll_result
23
+ # return one of the states, except the initial state
24
+ [:two, :three, :four, :five, :six][Random.rand(4)]
25
+ end
26
+
27
+ def display_dice_rolling_animation
28
+ # draw the new position of the dice
29
+ # p 'display dice'
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ require 'edge-state-machine'
2
+
3
+ class Microwave
4
+ include EdgeStateMachine
5
+
6
+ state_machine :microwave do # name should be optional, if the name is not present, it should have a default name
7
+ # we give state machines names, so we can pun many machines inside a class
8
+ initial_state :unplugged # initial state should be optional, if the initial state is not present, the initial state will be the first defined state
9
+
10
+ state :plugged
11
+
12
+ state :unplugged
13
+
14
+ state :door_opened do
15
+ enter :light_on # enter should be executed on entering the state
16
+ exit :light_off # exit method should be executed on exiting the state
17
+ end
18
+
19
+ state :door_closed
20
+
21
+ state :started_in_grill_mode do
22
+ enter lambda { |t| p "Entering hate" } # should have support for Procs
23
+ exit :grill_off
24
+ end
25
+
26
+ state :started do
27
+ enter :microwaves_on
28
+ exit :microwaves_off
29
+ end
30
+
31
+ event :plug_in do
32
+ transition :from => :unplugged, :to => :plugged
33
+ end
34
+
35
+ event :open_door do
36
+ transition :from => :plugged, :to => :door_opened
37
+ end
38
+
39
+ event :close_door do
40
+ transition :from => :door_opened, :to => :door_closed, :on_transition => :put_food_in_the_microwave # we can put many actions in an Array for the on_transition parameter
41
+ end
42
+
43
+ event :start do
44
+ transition :from => :door_closed, :to => [:started, :started_in_grill_mode], :on_transition => :start_spinning_the_food, :guard => :grill_button_pressed? # the method grill_button_pressed? should choose the next state
45
+ end
46
+
47
+ event :stop do
48
+ transition :from => [:started, :started_in_grill_mode], :to => :door_closed
49
+ end
50
+ end
51
+
52
+ def light_on
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ require 'edge-state-machine'
2
+
3
+ class User
4
+ include EdgeStateMachine
5
+
6
+ state_machine do
7
+ state :pending # first one is initial state
8
+ state :active
9
+ state :blocked # the user in this state can't sign in
10
+
11
+ event :activate do
12
+ transition :from => [:pending], :to => :active, :on_transition => :do_activate
13
+ end
14
+
15
+ event :block do
16
+ transition :from => [:pending, :active], :to => :blocked
17
+ end
18
+ end
19
+
20
+ def do_activate
21
+ # p 'do activate'
22
+ end
23
+ end
data/spec/state_spec.rb CHANGED
@@ -16,11 +16,11 @@ class Car
16
16
  state :driving
17
17
 
18
18
  event :turn_key do
19
- transitions :from => :parked, :to => :running, :on_transition => :start_engine
19
+ transition :from => :parked, :to => :running, :on_transition => :start_engine
20
20
  end
21
21
 
22
22
  event :start_driving do
23
- transitions :from => :parked, :to => :driving, :on_transition => [:start_engine, :loosen_handbrake, :push_gas_pedal]
23
+ transition :from => :parked, :to => :driving, :on_transition => [:start_engine, :loosen_handbrake, :push_gas_pedal]
24
24
  end
25
25
  end
26
26
 
@@ -33,7 +33,7 @@ class Car
33
33
  end
34
34
 
35
35
  def new_state(options={})
36
- EdgeStateMachine::State.new(@state_name, @options.merge(options))
36
+ EdgeStateMachine::State.new(@state_name)
37
37
  end
38
38
 
39
39
  describe EdgeStateMachine::State do
@@ -41,7 +41,6 @@ describe EdgeStateMachine::State do
41
41
  before do
42
42
  @state_name = :astate
43
43
  @machine = StateTestSubject.state_machine
44
- @options = { :crazy_custom_key => "key", :machine => @machine }
45
44
  end
46
45
 
47
46
  it "should set the name" do
@@ -52,13 +51,14 @@ describe EdgeStateMachine::State do
52
51
  new_state.display_name.should == "Astate"
53
52
  end
54
53
 
55
- it "should set the display_name from options" do
56
- new_state(:display => "A State").display_name.should == "A State"
54
+ it "should set the display_name from method" do
55
+ state = new_state
56
+ state.use_display_name('A State')
57
+ state.display_name.should == 'A State'
57
58
  end
58
59
 
59
60
  it "should set the options and expose them as options" do
60
- @options.delete(:machine)
61
- new_state.options.should == @options
61
+ new_state.options.should_not == nil
62
62
  end
63
63
 
64
64
  it "should be equal to a symbol of the same name" do
@@ -70,27 +70,32 @@ describe EdgeStateMachine::State do
70
70
  end
71
71
 
72
72
  it "should send a message to the record for an action if the action is present as a symbol" do
73
- state = new_state(:entering => :foo)
73
+ state = new_state
74
+ state.enter :foo
75
+
74
76
  record = mock
75
77
  record.should_receive(:foo)
76
- state.call_action(:entering, record)
78
+
79
+ state.execute_action(:enter, record)
77
80
  end
78
81
 
79
82
  it "should send a message to the record for an action if the action is present as a string" do
80
- state = new_state(:entering => "foo")
83
+ state = new_state
84
+ state.enter "foo"
81
85
 
82
86
  record = mock
83
87
  record.should_receive(:foo)
84
88
 
85
- state.call_action(:entering, record)
89
+ state.execute_action(:enter, record)
86
90
  end
87
91
 
88
92
  it "should call a proc, passing in the record for an action if the action is present" do
89
- state = new_state(:entering => Proc.new {|r| r.foobar})
93
+ state = new_state
94
+ state.exit Proc.new {|r| r.foobar}
90
95
 
91
96
  record = mock
92
97
  record.should_receive(:foobar)
93
98
 
94
- state.call_action(:entering, record)
99
+ state.execute_action(:exit, record)
95
100
  end
96
101
  end