edge-state-machine 0.0.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.
- data/.gitignore +4 -0
- data/.rspec +4 -0
- data/.travis.yml +1 -0
- data/Gemfile +4 -0
- data/README.rdoc +63 -0
- data/Rakefile +20 -0
- data/edge-state-machine.gemspec +31 -0
- data/lib/active_record/edge-state-machine.rb +34 -0
- data/lib/edge-state-machine/event.rb +107 -0
- data/lib/edge-state-machine/machine.rb +85 -0
- data/lib/edge-state-machine/state.rb +45 -0
- data/lib/edge-state-machine/state_transition.rb +47 -0
- data/lib/edge-state-machine/version.rb +3 -0
- data/lib/edge-state-machine.rb +71 -0
- data/lib/mongoid/edge-state-machine.rb +34 -0
- data/spec/active_record_helper.rb +10 -0
- data/spec/active_record_spec.rb +248 -0
- data/spec/event_spec.rb +76 -0
- data/spec/machine_spec.rb +44 -0
- data/spec/migrations/create_orders.rb +12 -0
- data/spec/migrations/create_traffic_lights.rb +8 -0
- data/spec/mongoid_helper.rb +12 -0
- data/spec/mongoid_spec.rb +251 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/state_spec.rb +191 -0
- metadata +167 -0
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'active_record_helper'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
4
|
+
|
5
|
+
class TrafficLight < ActiveRecord::Base
|
6
|
+
include ActiveRecord::EdgeStateMachine
|
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 ProtectedTrafficLight < TrafficLight
|
34
|
+
attr_protected :state
|
35
|
+
end
|
36
|
+
|
37
|
+
class ValidatingTrafficLight < TrafficLight
|
38
|
+
validate {|t| errors.add(:base, 'This TrafficLight will never validate after creation') unless t.new_record? }
|
39
|
+
end
|
40
|
+
|
41
|
+
class ConditionalValidatingTrafficLight < TrafficLight
|
42
|
+
validates :name, :presence => true, :length => { :within => 20..40 }, :confirmation => true, :if => :red?
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
class Order < ActiveRecord::Base
|
47
|
+
include ActiveRecord::EdgeStateMachine
|
48
|
+
|
49
|
+
state_machine do
|
50
|
+
state :opened
|
51
|
+
state :placed
|
52
|
+
state :paid
|
53
|
+
state :prepared
|
54
|
+
state :delivered
|
55
|
+
state :cancelled
|
56
|
+
|
57
|
+
# no timestamp col is being specified here - should be ignored
|
58
|
+
event :place do
|
59
|
+
transitions :from => :opened, :to => :placed
|
60
|
+
end
|
61
|
+
|
62
|
+
# should set paid_at timestamp
|
63
|
+
event :pay, :timestamp => true do
|
64
|
+
transitions :from => :placed, :to => :paid
|
65
|
+
end
|
66
|
+
|
67
|
+
# should set prepared_on
|
68
|
+
event :prepare, :timestamp => true do
|
69
|
+
transitions :from => :paid, :to => :prepared
|
70
|
+
end
|
71
|
+
|
72
|
+
# should set dispatched_at
|
73
|
+
event :deliver, :timestamp => "dispatched_at" do
|
74
|
+
transitions :from => :prepared, :to => :delivered
|
75
|
+
end
|
76
|
+
|
77
|
+
# should set cancellation_date
|
78
|
+
event :cancel, :timestamp => :cancellation_date do
|
79
|
+
transitions :from => [:placed, :paid, :prepared], :to => :cancelled
|
80
|
+
end
|
81
|
+
|
82
|
+
# should raise an exception as there is no timestamp col
|
83
|
+
event :reopen, :timestamp => true do
|
84
|
+
transitions :from => :cancelled, :to => :opened
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "active record state machine" do
|
91
|
+
|
92
|
+
context "existing active record" do
|
93
|
+
before do
|
94
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
95
|
+
ActiveRecord::Migration.verbose = false
|
96
|
+
CreateTrafficLights.migrate(:up)
|
97
|
+
@light = TrafficLight.create!
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should have an initial state" do
|
101
|
+
@light.off?.should == true
|
102
|
+
@light.current_state.should == :off
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should go to a valid state on transition" do
|
106
|
+
@light.reset
|
107
|
+
@light.red?.should == true
|
108
|
+
@light.current_state.should == :red
|
109
|
+
|
110
|
+
@light.green_on
|
111
|
+
@light.green?.should == true
|
112
|
+
@light.current_state.should == :green
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should not persist state on transition" do
|
116
|
+
@light.reset
|
117
|
+
@light.current_state.should == :red
|
118
|
+
@light.reload
|
119
|
+
@light.state.should == "off"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should persists state on transition" do
|
123
|
+
@light.reset!
|
124
|
+
@light.current_state.should == :red
|
125
|
+
@light.reload
|
126
|
+
@light.state.should == "red"
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should raise error on transition to an invalid state" do
|
130
|
+
expect { @light.yellow_on }.should raise_error EdgeStateMachine::InvalidTransition
|
131
|
+
@light.current_state.should == :off
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should persist state when state is protected on transition" do
|
135
|
+
protected_light = ProtectedTrafficLight.create!
|
136
|
+
protected_light.reset!
|
137
|
+
protected_light.current_state.should == :red
|
138
|
+
protected_light.reload
|
139
|
+
protected_light.state.should == "red"
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should not validate when try transition with wrong state " do
|
143
|
+
for s in @light.class.state_machine.states
|
144
|
+
@light.state = s.name
|
145
|
+
@light.valid?.should == true
|
146
|
+
end
|
147
|
+
@light.state = "invalid_one"
|
148
|
+
@light.valid?.should_not == true
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should raise exception when model validation fails on transition" do
|
152
|
+
validating_light = ValidatingTrafficLight.create!
|
153
|
+
expect {validating_light.reset!}.should raise_error ActiveRecord::RecordInvalid
|
154
|
+
validating_light.off?.should == true
|
155
|
+
end
|
156
|
+
|
157
|
+
#it "should state query method used in a validation condition" do
|
158
|
+
#validating_light = ConditionalValidatingTrafficLight.create!
|
159
|
+
#expect {validating_light.reset!}.should raise_error ActiveRecord::RecordInvalid
|
160
|
+
#validating_light.off?.should == true
|
161
|
+
#end
|
162
|
+
|
163
|
+
it "should reload the model when current state resets" do
|
164
|
+
@light.reset
|
165
|
+
@light.red?.should == true
|
166
|
+
@light.update_attribute(:state, 'green')
|
167
|
+
@light.reload.green?.should == false # reloaded state should come from instance variable not from database
|
168
|
+
# because the state can be changed without persist
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "new active record" do
|
173
|
+
before do
|
174
|
+
@light = TrafficLight.new
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should have the initial state set" do
|
178
|
+
@light.current_state.should == :off
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "timestamp" do
|
183
|
+
before do
|
184
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
185
|
+
ActiveRecord::Migration.verbose = false
|
186
|
+
CreateOrders.migrate(:up)
|
187
|
+
end
|
188
|
+
|
189
|
+
def create_order(state = nil)
|
190
|
+
Order.create! order_number: 234, state: state
|
191
|
+
end
|
192
|
+
|
193
|
+
# control case, no timestamp has been set so we should expect default behaviour
|
194
|
+
it "should not raise any exceptions when moving to placed" do
|
195
|
+
@order = create_order
|
196
|
+
expect { @order.place! }.should_not raise_error
|
197
|
+
@order.state.should == "placed"
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should set paid_at when moving to paid" do
|
201
|
+
@order = create_order(:placed)
|
202
|
+
@order.pay!
|
203
|
+
@order.reload
|
204
|
+
@order.paid_at.should_not be_nil
|
205
|
+
end
|
206
|
+
|
207
|
+
it "should set prepared_on when moving to prepared" do
|
208
|
+
@order = create_order(:paid)
|
209
|
+
@order.prepare!
|
210
|
+
@order.reload
|
211
|
+
@order.prepared_on.should_not be_nil
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should set dispatched_at when moving to delivered" do
|
215
|
+
@order = create_order(:prepared)
|
216
|
+
@order.deliver!
|
217
|
+
@order.reload
|
218
|
+
@order.dispatched_at.should_not be_nil
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should set cancellation_date when moving to cancelled" do
|
222
|
+
@order = create_order(:placed)
|
223
|
+
@order.cancel!
|
224
|
+
@order.reload
|
225
|
+
@order.cancellation_date.should_not be_nil
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should raise an exception as there is no attribute when moving to reopened" do
|
229
|
+
@order = create_order(:cancelled)
|
230
|
+
expect { @order.re_open! }.should raise_error NoMethodError
|
231
|
+
@order.reload
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should raise an exception when passing an invalid value to timestamp options" do
|
235
|
+
expect {
|
236
|
+
class Order < ActiveRecord::Base
|
237
|
+
include ActiveRecord::EdgeStateMachine
|
238
|
+
state_machine do
|
239
|
+
event :replace, timestamp: 1 do
|
240
|
+
transitions :from => :prepared, :to => :placed
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
}.should raise_error ArgumentError
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
data/spec/event_spec.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ArgumentsTestSubject
|
4
|
+
include EdgeStateMachine
|
5
|
+
attr_accessor :date
|
6
|
+
|
7
|
+
state_machine do
|
8
|
+
state :initial
|
9
|
+
state :opened
|
10
|
+
|
11
|
+
event :open do
|
12
|
+
transitions :from => :initial, :to => :opened, :on_transition => :update_date
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_date(date = Date.today)
|
17
|
+
self.date = date
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_event
|
22
|
+
@event = EdgeStateMachine::Event.new(nil, @state_name, {:success => @success}) do
|
23
|
+
transitions :to => :closed, :from => [:open, :received]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe EdgeStateMachine::Event do
|
28
|
+
|
29
|
+
before do
|
30
|
+
@state_name = :close_order
|
31
|
+
@success = :success_callback
|
32
|
+
@event = EdgeStateMachine::Event.new(nil, @state_name, {:success => @success}) do
|
33
|
+
transitions :to => :closed, :from => [:open, :received]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should set the name" do
|
38
|
+
@state_name.should == @event.name
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should set the success option" do
|
42
|
+
@success.should == @event.success
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should create StateTransitions" do
|
46
|
+
EdgeStateMachine::StateTransition.should_receive(:new).with(:to => :closed, :from => :open)
|
47
|
+
EdgeStateMachine::StateTransition.should_receive(:new).with(:to => :closed, :from => :received)
|
48
|
+
new_event
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "event arguments" do
|
52
|
+
it "should pass arguments to transition method" do
|
53
|
+
subject = ArgumentsTestSubject.new
|
54
|
+
subject.current_state.should == :initial
|
55
|
+
subject.open!(Date.strptime('2001-02-03', '%Y-%m-%d'))
|
56
|
+
subject.current_state.should == :opened
|
57
|
+
subject.date.should == Date.strptime('2001-02-03', '%Y-%m-%d')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "events being fired" do
|
62
|
+
it "should raise an EdgeStateMachine::InvalidTransition error if the transitions are empty" do
|
63
|
+
event = EdgeStateMachine::Event.new(nil, :event)
|
64
|
+
expect {event.fire(nil)}.should raise_error EdgeStateMachine::InvalidTransition
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should return the state of the first matching transition it finds" do
|
68
|
+
event = EdgeStateMachine::Event.new(nil, :event) do
|
69
|
+
transitions :to => :closed, :from => [:open, :received]
|
70
|
+
end
|
71
|
+
obj = mock
|
72
|
+
obj.stub!(:current_state).and_return(:open)
|
73
|
+
event.fire(obj).should == :closed
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class MachineTestSubject
|
4
|
+
include EdgeStateMachine
|
5
|
+
|
6
|
+
state_machine do
|
7
|
+
state :open
|
8
|
+
state :closed
|
9
|
+
end
|
10
|
+
|
11
|
+
state_machine :initial => :foo do
|
12
|
+
event :shutdown do
|
13
|
+
transitions :from => :open, :to => :closed
|
14
|
+
end
|
15
|
+
|
16
|
+
event :timeout do
|
17
|
+
transitions :from => :open, :to => :closed
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
state_machine :extra, :initial => :bar do
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe EdgeStateMachine::Machine do
|
26
|
+
|
27
|
+
it 'should allow reuse of existing machines' do
|
28
|
+
MachineTestSubject.state_machines.size.should == 2
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should set #initial_state from :initial option" do
|
32
|
+
MachineTestSubject.state_machine(:extra).initial_state.should == :bar
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should accesse non-default state machine" do
|
36
|
+
MachineTestSubject.state_machine(:extra).class.should == EdgeStateMachine::Machine
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should find event for given state" do
|
40
|
+
events = MachineTestSubject.state_machine.events_for(:open)
|
41
|
+
events.should be_include(:shutdown)
|
42
|
+
events.should be_include(:timeout)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateOrders < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table(:orders, force: true) do |t|
|
4
|
+
t.string :state
|
5
|
+
t.string :order_number
|
6
|
+
t.datetime :paid_at
|
7
|
+
t.datetime :prepared_on
|
8
|
+
t.datetime :dispatched_at
|
9
|
+
t.date :cancellation_date
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mongoid'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
require 'mongoid/edge-state-machine'
|
7
|
+
|
8
|
+
Mongoid.configure do |config|
|
9
|
+
config.master = Mongo::Connection.new.db('edge_state_machine_test')
|
10
|
+
config.allow_dynamic_fields = false
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'mongoid_helper'
|
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
|
+
describe "mongoid state machine" do
|
98
|
+
|
99
|
+
context "existing mongo document" do
|
100
|
+
before do
|
101
|
+
Mongoid.master.collections.reject { |c| c.name =~ /^system\./ }.each(&:drop)
|
102
|
+
@light = MongoTrafficLight.create!
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should have an initial state" do
|
106
|
+
@light.off?.should == true
|
107
|
+
@light.current_state.should == :off
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should go to a valid state on transition" do
|
111
|
+
@light.reset
|
112
|
+
@light.red?.should == true
|
113
|
+
@light.current_state.should == :red
|
114
|
+
|
115
|
+
@light.green_on
|
116
|
+
@light.green?.should == true
|
117
|
+
@light.current_state.should == :green
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should not persist state on transition" do
|
121
|
+
@light.reset
|
122
|
+
@light.current_state.should == :red
|
123
|
+
@light.reload
|
124
|
+
@light.state.should == "off"
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should persists state on transition" do
|
128
|
+
@light.reset!
|
129
|
+
@light.current_state.should == :red
|
130
|
+
@light.reload
|
131
|
+
@light.state.should == "red"
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should raise error on transition to an invalid state" do
|
135
|
+
expect { @light.yellow_on }.should raise_error EdgeStateMachine::InvalidTransition
|
136
|
+
@light.current_state.should == :off
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should persist state when state is protected on transition" do
|
140
|
+
protected_light = MongoProtectedTrafficLight.create!
|
141
|
+
protected_light.reset!
|
142
|
+
protected_light.current_state.should == :red
|
143
|
+
protected_light.reload
|
144
|
+
protected_light.state.should == "red"
|
145
|
+
end
|
146
|
+
|
147
|
+
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
|
150
|
+
@light.valid?.should == true
|
151
|
+
end
|
152
|
+
@light.state = "invalid_one"
|
153
|
+
@light.valid?.should_not == true
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should raise exception when model validation fails on transition" do
|
157
|
+
validating_light = MongoValidatingTrafficLight.create!
|
158
|
+
expect {validating_light.reset!}.should raise_error Mongoid::Errors::Validations
|
159
|
+
end
|
160
|
+
|
161
|
+
#it "should state query method used in a validation condition" do
|
162
|
+
#validating_light = MongoConditionalValidatingTrafficLight.create!
|
163
|
+
#expect {validating_light.reset!}.should raise_error Mongoid::RecordInvalid
|
164
|
+
#validating_light.off?.should == true
|
165
|
+
#end
|
166
|
+
|
167
|
+
it "should reload the model when current state resets" do
|
168
|
+
@light.reset
|
169
|
+
@light.red?.should == true
|
170
|
+
@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
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "new active record" do
|
177
|
+
before do
|
178
|
+
@light = MongoTrafficLight.new
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should have the initial state set" do
|
182
|
+
@light.current_state.should == :off
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context "timestamp" do
|
187
|
+
before do
|
188
|
+
Mongoid.master.collections.reject { |c| c.name =~ /^system\./ }.each(&:drop)
|
189
|
+
end
|
190
|
+
|
191
|
+
def create_order(state = nil)
|
192
|
+
MongoOrder.create! order_number: 234, state: state
|
193
|
+
end
|
194
|
+
|
195
|
+
# control case, no timestamp has been set so we should expect default behaviour
|
196
|
+
it "should not raise any exceptions when moving to placed" do
|
197
|
+
@order = create_order
|
198
|
+
expect { @order.place! }.should_not raise_error
|
199
|
+
@order.state.should == "placed"
|
200
|
+
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
|
+
end
|
251
|
+
end
|