yasm 0.0.3 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ Given /^a context$/ do
2
+ class ActionFeatureContext
3
+ include Yasm::Context
4
+
5
+ start :action_feature_state_1
6
+ end
7
+
8
+ class ActionFeatureState1
9
+ include Yasm::State
10
+ end
11
+
12
+ class ActionFeatureState2
13
+ include Yasm::State
14
+ end
15
+
16
+ @context = ActionFeatureContext.new
17
+ @context.state.value.class.should == ActionFeatureState1
18
+ end
19
+
20
+ When /^I apply an action to it that triggers a state transition$/ do
21
+ class TriggerActionFeatureState2
22
+ include Yasm::Action
23
+
24
+ triggers :action_feature_state_2
25
+ end
26
+ @context.do! TriggerActionFeatureState2
27
+ end
28
+
29
+ Then /^the state should transition appropriately$/ do
30
+ @context.state.value.class.should == ActionFeatureState2
31
+ end
@@ -0,0 +1,34 @@
1
+ Given /^a state that I intend to declare final$/ do
2
+ class FinalState; include Yasm::State; end
3
+ FinalState.final?.should be_false
4
+ end
5
+
6
+ When /^I declare it final$/ do
7
+ FinalState.final!
8
+ end
9
+
10
+ Then /^it should return true when asked if it's a FINAL state$/ do
11
+ FinalState.final?.should be_true
12
+ end
13
+
14
+ Given /^a final state$/ do
15
+ class ContextWithFinalState
16
+ include Yasm::Context
17
+ start :final_state
18
+ end
19
+
20
+ class FinalState
21
+ include Yasm::State
22
+ final!
23
+ end
24
+ @context = ContextWithFinalState.new
25
+ end
26
+
27
+ When /^I attempt to apply an action to it$/ do
28
+ class ImpossibleAction; include Yasm::Action; end
29
+ @apply_action = proc {@context.do! ImpossibleAction}
30
+ end
31
+
32
+ Then /^I should get an exception$/ do
33
+ @apply_action.should raise_exception(Yasm::FinalStateException)
34
+ end
@@ -0,0 +1,85 @@
1
+ Given /^a class that inherits from CouchRest::Model::Base$/ do
2
+ class TestYasmCouchPersistence < CouchRest::Model::Base
3
+ end
4
+ end
5
+
6
+ When /^I mix Yasm::Context into it$/ do
7
+ class TestYasmCouchPersistence
8
+ include Yasm::Context
9
+ end
10
+ end
11
+
12
+ Then /^Yasm::Persistence::CouchRest::Model should be autoincluded as well$/ do
13
+ TestYasmCouchPersistence.ancestors.should be_include(Yasm::Persistence::CouchRest::Model)
14
+ end
15
+
16
+ Given /^a couchrest model context$/ do
17
+ class CouchContext < CouchRest::Model::Base
18
+ use_database YASM_COUCH_DB
19
+ include Yasm::Context
20
+
21
+ start :couch_state1
22
+ end
23
+ class CouchState1
24
+ include Yasm::State
25
+ end
26
+ class CouchState2
27
+ include Yasm::State
28
+ end
29
+ class GoToState2
30
+ include Yasm::Action
31
+ triggers :couch_state2
32
+ end
33
+ @couch_context = CouchContext.new
34
+ end
35
+
36
+ When /^I save that context to CouchDB$/ do
37
+ @couch_context.do! GoToState2
38
+ @state_start_time = @couch_context.state.value.instantiated_at
39
+ @couch_context.save
40
+ end
41
+
42
+ Then /^the states should be saved in the document$/ do
43
+ @doc = YASM_COUCH_DB.get @couch_context.id
44
+ @doc["yasm"].should_not be_nil
45
+ @doc["yasm"]["states"][Yasm::Context::ANONYMOUS_STATE.to_s].should_not be_nil
46
+ @doc["yasm"]["states"][Yasm::Context::ANONYMOUS_STATE.to_s]["class"].should == "CouchState2"
47
+ Time.parse(@doc["yasm"]["states"][Yasm::Context::ANONYMOUS_STATE.to_s]["instantiated_at"]).to_s.should == @state_start_time.to_s
48
+ end
49
+
50
+ Given /^a couchrest model context with state saved in the database$/ do
51
+ class CouchContext < CouchRest::Model::Base
52
+ include Yasm::Context
53
+
54
+ start :couch_state1
55
+ end
56
+ class CouchState1; include Yasm::State; end
57
+ class CouchState2
58
+ include Yasm::State
59
+
60
+ maximum 9.minutes, :action => :go_to_state3
61
+ end
62
+ class CouchState3
63
+ include Yasm::State
64
+ end
65
+ class GoToState2; include Yasm::Action; triggers :couch_state2; end
66
+ class GoToState3; include Yasm::Action; triggers :couch_state3; end
67
+ @couch_context = CouchContext.new
68
+ @couch_context.do! GoToState2
69
+ @state_start_time = @couch_context.state.value.instantiated_at
70
+ @couch_context.save
71
+ end
72
+
73
+ When /^I load that context$/ do
74
+ ten_minutes_from_now = 10.minutes.from_now
75
+ Time.stub(:now).and_return ten_minutes_from_now
76
+ @couch_context = CouchContext.find @couch_context.id
77
+ end
78
+
79
+ Then /^the states should be restored$/ do
80
+ @couch_context.state.value.class.should == CouchState3
81
+ end
82
+
83
+ Then /^the states should be fast forwarded$/ do
84
+ @couch_context.state.value.class.should == CouchState3
85
+ end
@@ -0,0 +1,27 @@
1
+ Given /^a state$/ do
2
+ class StateWithLimitedActions
3
+ include Yasm::State
4
+ end
5
+ end
6
+
7
+ When /^I declare that only certain actions can be applied to it$/ do
8
+ StateWithLimitedActions.actions :limited_action1, :limited_action2
9
+ end
10
+
11
+ Then /^those actions should be stored on the state class$/ do
12
+ StateWithLimitedActions.allowed_actions.should == [:limited_action1, :limited_action2]
13
+ end
14
+
15
+ Given /^a state with limited actions$/ do
16
+ StateWithLimitedActions.actions :limited_action1, :limited_action2
17
+ end
18
+
19
+ Then /^I should be able to determine whether a given action is valid for a given state$/ do
20
+ class LimitedAction1; include Yasm::Action; end
21
+ class LimitedAction2; include Yasm::Action; end
22
+ class LimitedAction3; include Yasm::Action; end
23
+
24
+ StateWithLimitedActions.is_allowed?(LimitedAction1).should be_true
25
+ StateWithLimitedActions.is_allowed?(LimitedAction2).should be_true
26
+ StateWithLimitedActions.is_allowed?(LimitedAction3).should be_false
27
+ end
@@ -0,0 +1,147 @@
1
+ Given /^a state that I intend to declare a minimum time limit on$/ do
2
+ class StateWithMinimumTimeLimit
3
+ include Yasm::State
4
+ end
5
+ StateWithMinimumTimeLimit.minimum_duration.should be_nil
6
+ end
7
+
8
+ When /^I declare a minimum time limit on it$/ do
9
+ StateWithMinimumTimeLimit.minimum 10.minutes
10
+ end
11
+
12
+ Then /^it should store that time limit on the state class$/ do
13
+ StateWithMinimumTimeLimit.minimum_duration.should == 10.minutes
14
+ end
15
+
16
+ Given /^a state that has a minimum time limit$/ do
17
+ class ContextWithStateWithMinimumTimeLimit
18
+ include Yasm::Context
19
+ start :state_with_minimum_time_limit
20
+ end
21
+
22
+ class StateWithMinimumTimeLimit
23
+ include Yasm::State
24
+ minimum 10.minutes
25
+ end
26
+
27
+ @context = ContextWithStateWithMinimumTimeLimit.new
28
+ end
29
+
30
+ When /^I apply an action to that state before its minimum time limit has been reached$/ do
31
+ class ActionOnStateWithMinimumTimeLimit
32
+ include Yasm::Action
33
+ end
34
+
35
+ @apply_action = proc {@context.do! ActionOnStateWithMinimumTimeLimit}
36
+ end
37
+
38
+ Then /^I should get a minimum time limit exception$/ do
39
+ @apply_action.should raise_exception(Yasm::TimeLimitNotYetReached)
40
+ end
41
+
42
+ When /^I apply an action to that state after its minimum time limit has been reached$/ do
43
+ @apply_action = proc {
44
+ ten_minutes_from_now = 10.minutes.from_now
45
+ Time.stub(:now).and_return ten_minutes_from_now
46
+ @context.do! ActionOnStateWithMinimumTimeLimit
47
+ }
48
+ end
49
+
50
+ Then /^I should not get a minimum time limit exception$/ do
51
+ @apply_action.should_not raise_exception
52
+ end
53
+
54
+ Given /^a state that I intend to declare a maximum time limit on$/ do
55
+ class StateWithMaxTimeLimit
56
+ include Yasm::State
57
+ end
58
+ end
59
+
60
+ When /^I declare a maximum time limit on it without an action$/ do
61
+ @declaration = proc {
62
+ StateWithMaxTimeLimit.maximum 10.minutes
63
+ }
64
+ end
65
+
66
+ Then /^it should raise an argument error exception$/ do
67
+ @declaration.should raise_error(ArgumentError)
68
+ end
69
+
70
+ When /^I declare a maximum time limit on it with an action$/ do
71
+ @declaration = proc {
72
+ StateWithMaxTimeLimit.maximum 10.minutes, :action => :max_time_limit_action
73
+ }
74
+ class MaxTimeLimitAction; include Yasm::Action; end
75
+ end
76
+
77
+ Then /^it should store that maximum time limit and action on the state class$/ do
78
+ @declaration.should_not raise_error
79
+ StateWithMaxTimeLimit.maximum_duration.should == 10.minutes
80
+ StateWithMaxTimeLimit.maximum_duration_action.should == MaxTimeLimitAction
81
+ end
82
+
83
+ Given /^a context with the potential for a max time limit dominoe effect$/ do
84
+ class DominoContext
85
+ include Yasm::Context
86
+
87
+ start :domino1
88
+ end
89
+
90
+ class Domino1
91
+ include Yasm::State
92
+
93
+ actions :trigger_domino2
94
+ maximum 10.minutes, :action => :trigger_domino2
95
+ end
96
+
97
+ class TriggerDomino2
98
+ include Yasm::Action
99
+
100
+ triggers :domino2
101
+ end
102
+
103
+ class Domino2
104
+ include Yasm::State
105
+
106
+ maximum 10.minutes, :action => :trigger_domino3
107
+ end
108
+
109
+ class TriggerDomino3
110
+ include Yasm::Action
111
+
112
+ triggers :domino3
113
+ end
114
+
115
+ class Domino3
116
+ include Yasm::State
117
+ end
118
+
119
+ class TriggerDomino4
120
+ include Yasm::Action
121
+
122
+ triggers :domino4
123
+ end
124
+
125
+ class Domino4
126
+ include Yasm::State
127
+ end
128
+ end
129
+
130
+ When /^I wait long enough to cause the dominoe effect$/ do
131
+ @wait = proc {
132
+ @context = DominoContext.new
133
+ @context.state.value
134
+ thirty_minutes_from_now = 30.minutes.from_now
135
+ Time.stub(:now).and_return thirty_minutes_from_now
136
+ }
137
+ end
138
+
139
+ Then /^the dominoe effect should occur when I ask for the state$/ do
140
+ @wait.call
141
+ @context.state.value.class.should == Domino3
142
+ end
143
+
144
+ Then /^the dominoe effect should occur when I attempt to apply an action to the context$/ do
145
+ @context.do! TriggerDomino4
146
+ @context.state.value.class.should == Domino4
147
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift './lib'
2
+ require 'yasm'
3
+ require 'rspec'
4
+ require 'rspec/mocks/standalone'
5
+ require 'couchrest_model'
6
+
7
+ COUCHDB_SERVER = CouchRest.new "http://admin:password@localhost:5984"
8
+ YASM_COUCH_DB = COUCHDB_SERVER.database!('yasm_test')
9
+ COUCHDB_SERVER.default_database = 'yasm_test'
10
+
11
+ Before('@couch') do
12
+ YASM_COUCH_DB.recreate!
13
+ end
@@ -9,4 +9,6 @@ require 'yasm/action'
9
9
  require 'yasm/context/anonymous_state_identifier'
10
10
  require 'yasm/context/state_configuration'
11
11
  require 'yasm/context/state_container'
12
+ require 'yasm/context/state_configuration/action_hook'
12
13
  require 'yasm/context'
14
+ require 'yasm/persistence'
@@ -2,23 +2,32 @@ module Yasm
2
2
  module Context
3
3
  def self.included(base)
4
4
  base.extend ClassMethods
5
+ if defined?(CouchRest) and defined?(CouchRest::Model) and base.ancestors.include?(CouchRest::Model::Base) and !base.ancestors.include?(Yasm::Persistence::CouchRest::Model)
6
+ base.send :include, Yasm::Persistence::CouchRest::Model
7
+ end
5
8
  end
6
9
 
7
10
  module ClassMethods
11
+ def after_action(method, options={})
12
+ state_configuration(ANONYMOUS_STATE).after_action method, options
13
+ end
14
+
15
+ def before_action(method, options={})
16
+ state_configuration(ANONYMOUS_STATE).before_action method, options
17
+ end
18
+
8
19
  # for a simple, anonymous state
9
20
  def start(state)
10
- state_configurations[ANONYMOUS_STATE] = StateConfiguration.new
11
- state_configurations[ANONYMOUS_STATE].start state
21
+ state_configuration(ANONYMOUS_STATE).start state
12
22
  end
13
23
 
14
24
  # for a named state
15
25
  def state(name, &block)
16
26
  raise ArgumentError, "The state name must respond to `to_sym`" unless name.respond_to?(:to_sym)
17
27
  name = name.to_sym
18
- state_configurations[name] = StateConfiguration.new
19
- state_configurations[name].instance_eval &block
28
+ state_configuration(name).instance_eval &block
20
29
 
21
- raise "You must provide a start state for #{name}" unless state_configurations[name].start_state
30
+ raise "You must provide a start state for #{name}" unless state_configuration(name).start_state
22
31
 
23
32
  define_method(name) { state_container name }
24
33
  end
@@ -27,6 +36,11 @@ module Yasm
27
36
  def state_configurations
28
37
  @state_configurations ||= {}
29
38
  end
39
+
40
+ private
41
+ def state_configuration(name)
42
+ state_configurations[name] ||= StateConfiguration.new
43
+ end
30
44
  end
31
45
 
32
46
  def do!(*actions)
@@ -38,6 +52,10 @@ module Yasm
38
52
  state_container ANONYMOUS_STATE
39
53
  end
40
54
 
55
+ def fast_forward
56
+ self.class.state_configurations.keys.each { |state_name| state_container(state_name).value }
57
+ end
58
+
41
59
  private
42
60
  def state_containers
43
61
  @state_containers ||= {}
@@ -45,7 +63,7 @@ module Yasm
45
63
 
46
64
  def state_container(id)
47
65
  unless state_containers[id]
48
- state_containers[id] = StateContainer.new :context => self
66
+ state_containers[id] = StateContainer.new :context => self, :name => id
49
67
  Yasm::Manager.change_state :to => self.class.state_configurations[id].start_state, :on => state_containers[id]
50
68
  end
51
69
 
@@ -1,15 +1,25 @@
1
1
  module Yasm
2
2
  module Context
3
3
  class StateConfiguration
4
- attr_reader :start_state
5
-
4
+ attr_reader :start_state, :after_actions, :before_actions
5
+
6
6
  def initialize(start_state = nil)
7
7
  @start_state = start_state
8
+ @after_actions = []
9
+ @before_actions = []
8
10
  end
9
11
 
10
12
  def start(state)
11
13
  @start_state = state
12
14
  end
15
+
16
+ def after_action(method, options={})
17
+ @after_actions << ActionHook.new(method, options)
18
+ end
19
+
20
+ def before_action(method, options={})
21
+ @before_actions << ActionHook.new(method, options)
22
+ end
13
23
  end
14
24
  end
15
25
  end