yasm 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|