yasm 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.
- data/README.markdown +76 -0
- data/VERSION +1 -1
- data/lib/yasm/context/state_container.rb +51 -8
- data/lib/yasm/exceptions/time_limit_not_yet_reached.rb +1 -2
- data/lib/yasm/manager.rb +19 -27
- data/yasm.gemspec +2 -10
- metadata +6 -17
- data/spec/spec_helper.rb +0 -100
- data/spec/yasm/context_spec.rb +0 -65
- data/spec/yasm/conversions_spec.rb +0 -43
- data/spec/yasm/manager_spec.rb +0 -122
- data/spec/yasm/state_container_spec.rb +0 -62
- data/spec/yasm/state_spec.rb +0 -110
data/README.markdown
CHANGED
@@ -283,6 +283,82 @@ Now, if we create a vending machine, then wait at least a minute, next time we t
|
|
283
283
|
#==> Yasm::FinalStateException: We're sorry, but the current state `Obliterated` is final. It does not accept any actions.
|
284
284
|
|
285
285
|
|
286
|
+
## The Lazy Domino Effect
|
287
|
+
|
288
|
+
The maximum time limit on a state can cause a domino effect. For example, suppose the start state for your context has a max time limit. And the action that
|
289
|
+
runs when that time limit is reached transitions to a state with another max time limit. And so on. Now suppose you instantiate your context, and wait a reeeeealy
|
290
|
+
long time. Like, long enough to cause a state transition domino effect. Let's model this with a traffic light system:
|
291
|
+
|
292
|
+
class TrafficLight
|
293
|
+
include Yasm::Context
|
294
|
+
|
295
|
+
start :green
|
296
|
+
end
|
297
|
+
|
298
|
+
class Green
|
299
|
+
include Yasm::State
|
300
|
+
|
301
|
+
maximum 10.seconds, :action => :transition_to_yellow
|
302
|
+
end
|
303
|
+
|
304
|
+
class TransitionToYellow
|
305
|
+
include Yasm::Action
|
306
|
+
|
307
|
+
triggers :yellow
|
308
|
+
|
309
|
+
def execute
|
310
|
+
puts "transitioning to yellow."
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
class Yellow
|
315
|
+
include Yasm::State
|
316
|
+
|
317
|
+
maximum 3.seconds, :action => :transition_to_red
|
318
|
+
end
|
319
|
+
|
320
|
+
class TransitionToRed
|
321
|
+
include Yasm::Action
|
322
|
+
|
323
|
+
triggers :red
|
324
|
+
|
325
|
+
def execute
|
326
|
+
puts "transitioning to red."
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
class Red
|
331
|
+
include Yasm::State
|
332
|
+
|
333
|
+
maximum 13.seconds, :action => :transition_to_green
|
334
|
+
end
|
335
|
+
|
336
|
+
class TransitionToGreen
|
337
|
+
include Yasm::Action
|
338
|
+
|
339
|
+
triggers :green
|
340
|
+
|
341
|
+
def execute
|
342
|
+
puts "transitioning to green."
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
t = TrafficLight.new
|
347
|
+
|
348
|
+
puts t.state.value
|
349
|
+
#==> Green
|
350
|
+
|
351
|
+
sleep 30
|
352
|
+
|
353
|
+
t.state.value
|
354
|
+
#==> "transitioning to yellow."
|
355
|
+
#==> "transitioning to red."
|
356
|
+
#==> "transitioning to green."
|
357
|
+
#==> Green
|
358
|
+
|
359
|
+
Notice that this domino effect happened lazily when you call the `do!` method, or the `context.state.value` methods. Quite nice for systems where
|
360
|
+
you persist your state to a db.
|
361
|
+
|
286
362
|
## PUBLIC DOMAIN
|
287
363
|
|
288
364
|
This software is committed to the public domain. No license. No copyright. DO ANYTHING!
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
@@ -9,8 +9,13 @@ module Yasm
|
|
9
9
|
@state = options[:state]
|
10
10
|
end
|
11
11
|
|
12
|
-
def value;
|
13
|
-
|
12
|
+
def value; state; end
|
13
|
+
|
14
|
+
def state
|
15
|
+
check_maximum
|
16
|
+
@state
|
17
|
+
end
|
18
|
+
|
14
19
|
def state=(s)
|
15
20
|
if s.class == Class
|
16
21
|
@state = s.new
|
@@ -20,13 +25,51 @@ module Yasm
|
|
20
25
|
end
|
21
26
|
|
22
27
|
def do!(*actions)
|
23
|
-
actions
|
28
|
+
actions.each do |action|
|
29
|
+
fire! action
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def fire!(action)
|
35
|
+
check_maximum
|
24
36
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
37
|
+
# Verify that the action is possible given the current state
|
38
|
+
if !@state.reached_minimum_time_limit?
|
39
|
+
raise Yasm::TimeLimitNotYetReached, "We're sorry, but the time limit on the state `#{@state}` has not yet been reached."
|
40
|
+
elsif @state.class.final?
|
41
|
+
raise Yasm::FinalStateException, "We're sorry, but the current state `#{@state}` is final. It does not accept any actions."
|
42
|
+
elsif !@state.class.is_allowed?(action.class)
|
43
|
+
raise Yasm::InvalidActionException, "We're sorry, but the action `#{action.class}` is not possible given the current state `#{@state}`."
|
44
|
+
end
|
45
|
+
|
46
|
+
action = Yasm::Manager.setup_action :action => action, :context => context, :state_container => self
|
47
|
+
Yasm::Manager.change_state(
|
48
|
+
:to => action.class.trigger_state,
|
49
|
+
:on => self
|
50
|
+
) if action.class.trigger_state
|
51
|
+
Yasm::Manager.execute_action action
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_maximum
|
55
|
+
while @state.passed_maximum_time_limit?
|
56
|
+
# setup the action that should be performed when a state has lasted too long
|
57
|
+
action = Yasm::Manager.setup_action(
|
58
|
+
:action => @state.class.maximum_duration_action,
|
59
|
+
:context => context,
|
60
|
+
:state_container => self
|
61
|
+
)
|
62
|
+
|
63
|
+
# update the state
|
64
|
+
Yasm::Manager.change_state(
|
65
|
+
:to => action.class.trigger_state || @state.class,
|
66
|
+
:on => self,
|
67
|
+
:at => @state.instantiated_at + @state.class.maximum_duration
|
68
|
+
)
|
69
|
+
|
70
|
+
# execute the action
|
71
|
+
Yasm::Manager.execute_action action
|
72
|
+
end
|
30
73
|
end
|
31
74
|
end
|
32
75
|
end
|
data/lib/yasm/manager.rb
CHANGED
@@ -5,40 +5,32 @@ module Yasm
|
|
5
5
|
def change_state(options)
|
6
6
|
new_state = options[:to]
|
7
7
|
state_container = options[:on]
|
8
|
+
state_time = options[:at] || Time.now
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
"We're sorry, but the time limit on the state `#{state_container.state}` has not yet been reached."
|
12
|
-
) if state_container.state and !state_container.state.reached_minimum_time_limit?
|
13
|
-
|
14
|
-
new_state = new_state.to_class if new_state.respond_to? :to_class
|
15
|
-
new_state = new_state.new
|
16
|
-
new_state.instantiated_at = Time.now
|
17
|
-
|
10
|
+
new_state = get_instance new_state
|
11
|
+
new_state.instantiated_at = state_time
|
18
12
|
state_container.state = new_state
|
19
13
|
end
|
20
14
|
|
21
|
-
def
|
15
|
+
def setup_action(options)
|
16
|
+
action = options[:action]
|
22
17
|
context = options[:context]
|
23
|
-
actions = options[:actions]
|
24
18
|
state_container = options[:state_container]
|
19
|
+
|
20
|
+
action = get_instance action
|
21
|
+
action.context = context
|
22
|
+
action.state_container = state_container
|
23
|
+
action
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute_action(action)
|
27
|
+
action.execute
|
28
|
+
end
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# Verify that the action is possible given the current state
|
33
|
-
if state_container.state.class.final?
|
34
|
-
raise Yasm::FinalStateException, "We're sorry, but the current state `#{state_container.state}` is final. It does not accept any actions."
|
35
|
-
elsif !state_container.state.class.is_allowed?(action.class)
|
36
|
-
raise Yasm::InvalidActionException, "We're sorry, but the action `#{action.class}` is not possible given the current state `#{state_container.state}`."
|
37
|
-
end
|
38
|
-
|
39
|
-
change_state :to => action.triggers.to_class, :on => state_container if action.triggers
|
40
|
-
action.execute
|
41
|
-
end
|
30
|
+
def get_instance(obj)
|
31
|
+
obj = obj.to_class if obj.respond_to? :to_class
|
32
|
+
obj = obj.new if obj.class == Class
|
33
|
+
obj
|
42
34
|
end
|
43
35
|
end
|
44
36
|
end
|
data/yasm.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{yasm}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Matt Parker"]
|
12
|
-
s.date = %q{2011-02-
|
12
|
+
s.date = %q{2011-02-17}
|
13
13
|
s.description = %q{Breaks up states, actions, and contexts into seperate classes.moonmaster9000@gmail.com}
|
14
14
|
s.email = %q{moonmaster9000@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -39,14 +39,6 @@ Gem::Specification.new do |s|
|
|
39
39
|
s.require_paths = ["lib"]
|
40
40
|
s.rubygems_version = %q{1.5.0}
|
41
41
|
s.summary = %q{Yet Another State Machine. Pronounced "yaz-um."}
|
42
|
-
s.test_files = [
|
43
|
-
"spec/spec_helper.rb",
|
44
|
-
"spec/yasm/context_spec.rb",
|
45
|
-
"spec/yasm/conversions_spec.rb",
|
46
|
-
"spec/yasm/manager_spec.rb",
|
47
|
-
"spec/yasm/state_container_spec.rb",
|
48
|
-
"spec/yasm/state_spec.rb"
|
49
|
-
]
|
50
42
|
|
51
43
|
if s.respond_to? :specification_version then
|
52
44
|
s.specification_version = 3
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yasm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt Parker
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-17 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -77,12 +77,6 @@ files:
|
|
77
77
|
- lib/yasm/version.rb
|
78
78
|
- yasm.gemspec
|
79
79
|
- README.markdown
|
80
|
-
- spec/spec_helper.rb
|
81
|
-
- spec/yasm/context_spec.rb
|
82
|
-
- spec/yasm/conversions_spec.rb
|
83
|
-
- spec/yasm/manager_spec.rb
|
84
|
-
- spec/yasm/state_container_spec.rb
|
85
|
-
- spec/yasm/state_spec.rb
|
86
80
|
has_rdoc: true
|
87
81
|
homepage: http://github.com/moonmaster9000/yasm
|
88
82
|
licenses: []
|
@@ -117,10 +111,5 @@ rubygems_version: 1.5.0
|
|
117
111
|
signing_key:
|
118
112
|
specification_version: 3
|
119
113
|
summary: Yet Another State Machine. Pronounced "yaz-um."
|
120
|
-
test_files:
|
121
|
-
|
122
|
-
- spec/yasm/context_spec.rb
|
123
|
-
- spec/yasm/conversions_spec.rb
|
124
|
-
- spec/yasm/manager_spec.rb
|
125
|
-
- spec/yasm/state_container_spec.rb
|
126
|
-
- spec/yasm/state_spec.rb
|
114
|
+
test_files: []
|
115
|
+
|
data/spec/spec_helper.rb
DELETED
@@ -1,100 +0,0 @@
|
|
1
|
-
$LOAD_PATH.unshift './lib'
|
2
|
-
require 'yasm'
|
3
|
-
|
4
|
-
# class VendingMachine
|
5
|
-
# include Yasm::Context
|
6
|
-
#
|
7
|
-
# start Waiting
|
8
|
-
# end
|
9
|
-
#
|
10
|
-
# class Waiting
|
11
|
-
# include Yasm::State
|
12
|
-
# end
|
13
|
-
#
|
14
|
-
# class Vending
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# class InputMoney
|
18
|
-
# include Yasm::Action
|
19
|
-
#
|
20
|
-
# triggers Vending
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# class VendingMachine
|
25
|
-
# include Yasm::Context
|
26
|
-
#
|
27
|
-
# # start Waiting
|
28
|
-
# #
|
29
|
-
# # state :status do
|
30
|
-
# # start Waiting
|
31
|
-
# # end
|
32
|
-
# #
|
33
|
-
# attr_accessor :money
|
34
|
-
# def initialize
|
35
|
-
# @money = 0
|
36
|
-
# end
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# class Waiting
|
40
|
-
# include Yasm::State
|
41
|
-
#
|
42
|
-
# actions InputMoney
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# class Vending
|
46
|
-
# include Yasm::State
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# class NotAState
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# class InputMoney
|
53
|
-
# include Yasm::Action
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# class Vend
|
57
|
-
# include Yasm::Action
|
58
|
-
# triggers Vending
|
59
|
-
#
|
60
|
-
# def initialize(item)
|
61
|
-
# @item = item
|
62
|
-
# end
|
63
|
-
#
|
64
|
-
# def execute
|
65
|
-
# puts "Vending #{@item}"
|
66
|
-
# context.do! Refund if context.money > 0
|
67
|
-
# end
|
68
|
-
# end
|
69
|
-
#
|
70
|
-
# class Refund
|
71
|
-
# include Yasm::Action
|
72
|
-
#
|
73
|
-
# triggers Waiting
|
74
|
-
#
|
75
|
-
# def execute
|
76
|
-
# puts "Refunding #{context.money}"
|
77
|
-
# context.money = 0
|
78
|
-
# end
|
79
|
-
# end
|
80
|
-
#
|
81
|
-
# class MakeSelection
|
82
|
-
# include Yasm::Action
|
83
|
-
#
|
84
|
-
# def initialize(selection)
|
85
|
-
# @selection = selection
|
86
|
-
# end
|
87
|
-
#
|
88
|
-
# def execute
|
89
|
-
# if context.money >= selection.price
|
90
|
-
# trigger Vending
|
91
|
-
# context.money -= selection.price
|
92
|
-
# context.do! Vend.new(selection)
|
93
|
-
# else
|
94
|
-
# puts "You must enter more money."
|
95
|
-
# end
|
96
|
-
# end
|
97
|
-
# end
|
98
|
-
#
|
99
|
-
# class NotAnAction
|
100
|
-
# end
|
data/spec/yasm/context_spec.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Yasm::Context do
|
4
|
-
context "when included in a class" do
|
5
|
-
before do
|
6
|
-
class VendingMachine
|
7
|
-
include Yasm::Context
|
8
|
-
end
|
9
|
-
|
10
|
-
class Waiting
|
11
|
-
include Yasm::State
|
12
|
-
end
|
13
|
-
|
14
|
-
class InputMoney
|
15
|
-
include Yasm::Action
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe "##state" do
|
20
|
-
it "should require something that can be converted to a symbol" do
|
21
|
-
proc { VendingMachine.state(nil) }.should raise_exception(ArgumentError, "The state name must respond to `to_sym`")
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should create a new state configuration" do
|
25
|
-
VendingMachine.state_configurations[:electricity].should be_nil
|
26
|
-
class On; include Yasm::State; end
|
27
|
-
VendingMachine.state(:electricity) { start :on }
|
28
|
-
VendingMachine.state_configurations[:electricity].should_not be_nil
|
29
|
-
VendingMachine.state_configurations[:electricity].start_state.should == :on
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should instance_eval the block on the new state configuration" do
|
33
|
-
VendingMachine.state_configurations[:power].should be_nil
|
34
|
-
class On; include Yasm::State; end
|
35
|
-
VendingMachine.state(:power) { start :on }
|
36
|
-
VendingMachine.state_configurations[:power].should_not be_nil
|
37
|
-
VendingMachine.state_configurations[:power].start_state.should == :on
|
38
|
-
end
|
39
|
-
|
40
|
-
it "should create an instance method that returns the state" do
|
41
|
-
class On; include Yasm::State; end
|
42
|
-
VendingMachine.state(:light) { start :on }
|
43
|
-
VendingMachine.new.light.state.class.should == On
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
describe "##start" do
|
48
|
-
context "when called directly on the context" do
|
49
|
-
it "should store the state as the start state of the anonymous state configuration" do
|
50
|
-
VendingMachine.state_configurations[Yasm::Context::ANONYMOUS_STATE].should be_nil
|
51
|
-
VendingMachine.start :waiting
|
52
|
-
VendingMachine.state_configurations[Yasm::Context::ANONYMOUS_STATE].start_state.should == :waiting
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe "#do!" do
|
58
|
-
it "should pass all actions passed to it off to the anonymous state_container #do! method" do
|
59
|
-
v = VendingMachine.new
|
60
|
-
v.state.should_receive(:do!).with(InputMoney, InputMoney)
|
61
|
-
v.do! InputMoney, InputMoney
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Yasm::Conversions::Symbol do
|
4
|
-
it "should create a :to_class instance method on Symbols" do
|
5
|
-
:sym.respond_to?(:to_class).should be_true
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
describe Symbol do
|
10
|
-
describe "#to_class" do
|
11
|
-
it "should convert the symbol to a class" do
|
12
|
-
class SymbolTest; end
|
13
|
-
:symbol_test.to_class.should == SymbolTest
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should be the inverse of the Class #to_sym class method" do
|
17
|
-
class SymbolTest; end
|
18
|
-
:symbol_test.to_class.to_sym.should == :symbol_test
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
describe Yasm::Conversions::Class do
|
25
|
-
it "should create a :to_sym class method on classes" do
|
26
|
-
class SymbolTest; end
|
27
|
-
SymbolTest.respond_to?(:to_sym).should be_true
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe Symbol do
|
32
|
-
describe "#to_sym" do
|
33
|
-
it "should convert the class to a symbol" do
|
34
|
-
class SymbolTest; end
|
35
|
-
SymbolTest.to_sym.should == :symbol_test
|
36
|
-
end
|
37
|
-
|
38
|
-
it "should be the inverse of the Symbol #to_class instance method" do
|
39
|
-
class SymbolTest; end
|
40
|
-
SymbolTest.to_sym.to_class.should == SymbolTest
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
data/spec/yasm/manager_spec.rb
DELETED
@@ -1,122 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Yasm::Manager do
|
4
|
-
before do
|
5
|
-
class VendingMachine
|
6
|
-
include Yasm::Context
|
7
|
-
|
8
|
-
start :on
|
9
|
-
end
|
10
|
-
|
11
|
-
class Unplug
|
12
|
-
include Yasm::Action
|
13
|
-
|
14
|
-
triggers :off
|
15
|
-
end
|
16
|
-
|
17
|
-
class PlugIn
|
18
|
-
include Yasm::Action
|
19
|
-
|
20
|
-
triggers :on
|
21
|
-
end
|
22
|
-
|
23
|
-
class Destroy
|
24
|
-
include Yasm::Action
|
25
|
-
|
26
|
-
triggers :destroyed
|
27
|
-
end
|
28
|
-
|
29
|
-
class On
|
30
|
-
include Yasm::State
|
31
|
-
|
32
|
-
actions :unplug, :destroy
|
33
|
-
end
|
34
|
-
|
35
|
-
class Off
|
36
|
-
include Yasm::State
|
37
|
-
|
38
|
-
actions :plug_in, :destroy
|
39
|
-
end
|
40
|
-
|
41
|
-
class Destroyed
|
42
|
-
include Yasm::State
|
43
|
-
|
44
|
-
final!
|
45
|
-
end
|
46
|
-
|
47
|
-
@vending_machine = VendingMachine.new
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
describe "##change_state" do
|
53
|
-
it "should convert the state to a class if we pass an object that respond to :to_class" do
|
54
|
-
Destroyed.should_receive(:to_class).and_return Destroyed
|
55
|
-
Yasm::Manager.change_state :to => Destroyed, :on => @vending_machine.state
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should set the instantiated_at property on the state to the current time" do
|
59
|
-
seconds_since_the_epoch = Time.now
|
60
|
-
Time.should_receive(:now).twice.and_return seconds_since_the_epoch
|
61
|
-
Yasm::Manager.change_state :to => On, :on => @vending_machine.state
|
62
|
-
@vending_machine.state.value.instantiated_at.should == seconds_since_the_epoch
|
63
|
-
end
|
64
|
-
|
65
|
-
it "should raise an exception if the current state has not yet reached it's time limit" do
|
66
|
-
class TenSeconds
|
67
|
-
include Yasm::State
|
68
|
-
|
69
|
-
minimum 10.seconds
|
70
|
-
end
|
71
|
-
|
72
|
-
proc {
|
73
|
-
Yasm::Manager.change_state :to => TenSeconds, :on => @vending_machine.state
|
74
|
-
Yasm::Manager.change_state :to => On, :on => @vending_machine.state
|
75
|
-
}.should raise_exception(Yasm::TimeLimitNotYetReached, "We're sorry, but the time limit on the state `TenSeconds` has not yet been reached.")
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe "##execute" do
|
80
|
-
it "should apply each action, sequentially, to the appropriate state_container within the context" do
|
81
|
-
@vending_machine.state.value.class.should == On
|
82
|
-
|
83
|
-
Yasm::Manager.execute :context => @vending_machine, :state_container => @vending_machine.state, :actions => [Unplug]
|
84
|
-
@vending_machine.state.value.class.should == Off
|
85
|
-
|
86
|
-
Yasm::Manager.execute :context => @vending_machine, :state_container => @vending_machine.state, :actions => [PlugIn]
|
87
|
-
@vending_machine.state.value.class.should == On
|
88
|
-
end
|
89
|
-
|
90
|
-
it "should raise an exception if you attempt to execute an action that isn't allowed by a state" do
|
91
|
-
@vending_machine.state.value.class.should == On
|
92
|
-
|
93
|
-
proc {
|
94
|
-
Yasm::Manager.execute(
|
95
|
-
:context => @vending_machine,
|
96
|
-
:state_container => @vending_machine.state,
|
97
|
-
:actions => [PlugIn]
|
98
|
-
)
|
99
|
-
}.should raise_exception(Yasm::InvalidActionException, "We're sorry, but the action `PlugIn` is not possible given the current state `On`.")
|
100
|
-
end
|
101
|
-
|
102
|
-
it "should raise an exception if you attempt to execute an action on a final state." do
|
103
|
-
proc {
|
104
|
-
Yasm::Manager.execute(
|
105
|
-
:context => @vending_machine,
|
106
|
-
:state_container => @vending_machine.state,
|
107
|
-
:actions => [Destroy, PlugIn]
|
108
|
-
)
|
109
|
-
}.should raise_exception(Yasm::FinalStateException, "We're sorry, but the current state `Destroyed` is final. It does not accept any actions.")
|
110
|
-
end
|
111
|
-
|
112
|
-
it "should not raise an exception if the action is allowed by the state" do
|
113
|
-
proc {
|
114
|
-
Yasm::Manager.execute(
|
115
|
-
:context => @vending_machine,
|
116
|
-
:state_container => @vending_machine.state,
|
117
|
-
:actions => [Unplug]
|
118
|
-
)
|
119
|
-
}.should_not raise_exception
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Yasm::Context::StateContainer do
|
4
|
-
before do
|
5
|
-
class VendingMachine
|
6
|
-
include Yasm::Context
|
7
|
-
|
8
|
-
start :waiting
|
9
|
-
end
|
10
|
-
|
11
|
-
class Waiting
|
12
|
-
include Yasm::State
|
13
|
-
end
|
14
|
-
|
15
|
-
class Hit
|
16
|
-
include Yasm::Action
|
17
|
-
|
18
|
-
triggers :jammed
|
19
|
-
end
|
20
|
-
|
21
|
-
class Jammed
|
22
|
-
include Yasm::State
|
23
|
-
|
24
|
-
maximum 10.seconds, :action => :explode
|
25
|
-
end
|
26
|
-
|
27
|
-
class Exploded
|
28
|
-
include Yasm::State
|
29
|
-
|
30
|
-
actions :clean_up
|
31
|
-
end
|
32
|
-
|
33
|
-
class CleanUp
|
34
|
-
include Yasm::Action
|
35
|
-
end
|
36
|
-
|
37
|
-
class Explode
|
38
|
-
include Yasm::Action
|
39
|
-
end
|
40
|
-
|
41
|
-
class InputMoney
|
42
|
-
include Yasm::Action
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
describe "#do!" do
|
47
|
-
it "should pass the actions off to the Yasm::Manager.execute method" do
|
48
|
-
v = VendingMachine.new
|
49
|
-
Yasm::Manager.should_receive(:execute).with(:context => v, :state_container => v.state, :actions => [InputMoney, InputMoney])
|
50
|
-
v.state.do! InputMoney, InputMoney
|
51
|
-
end
|
52
|
-
|
53
|
-
it "should add the maximum action to the front of the action list if the maximum time limit has been reached" do
|
54
|
-
v = VendingMachine.new
|
55
|
-
v.do! Hit
|
56
|
-
Yasm::Manager.should_receive(:execute).with(:context => v, :state_container => v.state, :actions => [Explode, CleanUp])
|
57
|
-
time = 10.seconds.from_now
|
58
|
-
Time.stub!(:now).and_return time
|
59
|
-
v.do! CleanUp
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
data/spec/yasm/state_spec.rb
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Yasm::State do
|
4
|
-
before do
|
5
|
-
class TestState
|
6
|
-
include Yasm::State
|
7
|
-
end
|
8
|
-
|
9
|
-
class Action1; include Yasm::Action; end
|
10
|
-
class Action2; include Yasm::Action; end
|
11
|
-
end
|
12
|
-
|
13
|
-
describe "##actions" do
|
14
|
-
it "should set the @allowed_actions to the input to this method" do
|
15
|
-
TestState.actions Action1, Action2
|
16
|
-
TestState.allowed_actions.should == [Action1, Action2]
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
describe "##final!" do
|
21
|
-
it "should set the @allowed_actions to an empty array" do
|
22
|
-
TestState.final!
|
23
|
-
TestState.allowed_actions.should == []
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "##final?" do
|
28
|
-
it "should return true if there are no allowed actions" do
|
29
|
-
TestState.final!
|
30
|
-
TestState.final?.should be_true
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe "##minimum" do
|
35
|
-
it "should require an integer" do
|
36
|
-
proc { TestState.minimum "10 minutes" }.should raise_exception("You must provide a Fixnum to the ##minimum method (represents number of seconds). For example: 2.minutes")
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should set the @state_minimum_duration to the number input" do
|
40
|
-
TestState.minimum 1.minute
|
41
|
-
TestState.minimum_duration.should == 60
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
describe "#reached_minimum_time_limit?" do
|
46
|
-
before do
|
47
|
-
class MinState
|
48
|
-
include Yasm::State
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should return true if there is no minimum time limit for the state" do
|
53
|
-
MinState.new.reached_minimum_time_limit?.should be_true
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should return false if there is a time limit that hasn't been reached yet" do
|
57
|
-
MinState.minimum 10.seconds
|
58
|
-
state = MinState.new
|
59
|
-
state.instantiated_at = Time.now
|
60
|
-
state.reached_minimum_time_limit?.should be_false
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should return true if the state has reached it's time limit" do
|
64
|
-
MinState.minimum 10.seconds
|
65
|
-
state = MinState.new
|
66
|
-
state.instantiated_at = Time.now
|
67
|
-
ten_seconds_from_now = 10.seconds.from_now
|
68
|
-
Time.should_receive(:now).and_return ten_seconds_from_now
|
69
|
-
state.reached_minimum_time_limit?
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
describe "##maximum" do
|
74
|
-
it "should require both a time limit and an action" do
|
75
|
-
proc { TestState.maximum }.should raise_exception(ArgumentError)
|
76
|
-
proc { TestState.maximum 10.seconds }.should raise_exception(ArgumentError)
|
77
|
-
proc { TestState.maximum 10.seconds, :action => :action1 }.should_not raise_exception
|
78
|
-
end
|
79
|
-
|
80
|
-
it "should store the time limit and action" do
|
81
|
-
TestState.maximum 20.seconds, :action => :action2
|
82
|
-
TestState.maximum_duration.should == 20.seconds
|
83
|
-
TestState.maximum_duration_action.should == Action2
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "##passed_maximum_time_limit?" do
|
88
|
-
it "should return false if no time limit has been set" do
|
89
|
-
class UnlimitedState
|
90
|
-
include Yasm::State
|
91
|
-
end
|
92
|
-
|
93
|
-
UnlimitedState.new.passed_maximum_time_limit?.should be_false
|
94
|
-
end
|
95
|
-
|
96
|
-
it "should return true if a time limit was set, and that limit has been passed" do
|
97
|
-
class LimitedState
|
98
|
-
include Yasm::State
|
99
|
-
|
100
|
-
maximum 10.seconds, :action => :action2
|
101
|
-
end
|
102
|
-
|
103
|
-
s = LimitedState.new
|
104
|
-
s.instantiated_at = Time.now
|
105
|
-
ten_seconds_from_now = 10.seconds.from_now
|
106
|
-
Time.stub!(:now).and_return ten_seconds_from_now
|
107
|
-
s.passed_maximum_time_limit?.should be_true
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|