state_flow 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +61 -23
- data/VERSION +1 -1
- data/lib/state_flow/action.rb +22 -56
- data/lib/state_flow/action_client.rb +16 -0
- data/lib/state_flow/action_event.rb +21 -0
- data/lib/state_flow/base.rb +71 -28
- data/lib/state_flow/context.rb +82 -0
- data/lib/state_flow/element.rb +60 -0
- data/lib/state_flow/element_visitable.rb +19 -0
- data/lib/state_flow/event.rb +13 -13
- data/lib/state_flow/event_client.rb +88 -0
- data/lib/state_flow/exception_handler.rb +39 -0
- data/lib/state_flow/exception_handler_client.rb +25 -0
- data/lib/state_flow/guard.rb +21 -0
- data/lib/state_flow/guard_client.rb +28 -0
- data/lib/state_flow/named_event.rb +56 -0
- data/lib/state_flow/named_guard.rb +17 -0
- data/lib/state_flow/state.rb +91 -0
- data/lib/state_flow.rb +20 -6
- data/spec/database.yml +0 -2
- data/spec/order_spec.rb +415 -0
- data/spec/resources/models/order.rb +171 -0
- data/spec/schema.rb +2 -7
- data/spec/spec_helper.rb +1 -3
- data/spec/state_flow/state_spec.rb +149 -0
- metadata +21 -13
- data/lib/state_flow/builder.rb +0 -193
- data/lib/state_flow/entry.rb +0 -44
- data/lib/state_flow/named_action.rb +0 -19
- data/spec/base_spec.rb +0 -457
- data/spec/concurrency_spec.rb +0 -275
- data/spec/page_spec.rb +0 -554
- data/spec/resources/models/page.rb +0 -70
@@ -0,0 +1,149 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require File.join(File.dirname(__FILE__), '../spec_helper')
|
3
|
+
|
4
|
+
describe StateFlow::State do
|
5
|
+
|
6
|
+
describe "with Order" do
|
7
|
+
describe "from waiting_settling" do
|
8
|
+
describe ":cash_on_delivery" do
|
9
|
+
before do
|
10
|
+
StateFlow::Log.delete_all
|
11
|
+
Order.delete_all
|
12
|
+
@order = Order.new
|
13
|
+
@order.product_name = "Beautiful Code"
|
14
|
+
@order.payment_type = :cash_on_delivery
|
15
|
+
@order.status_key = :waiting_settling
|
16
|
+
@order.save!
|
17
|
+
Order.count == 1
|
18
|
+
|
19
|
+
@flow = Order.state_flow_for(:status_cd)
|
20
|
+
@state = @flow.origin
|
21
|
+
@state.name.should == :waiting_settling
|
22
|
+
|
23
|
+
@context = @flow.prepare_context(@order)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should recieve guard process" do
|
27
|
+
g0 = @state.guards[0] # :pay_cash_on_delivery?
|
28
|
+
g0.should_receive(:process).with(@context).and_return{|order| g0.action.process(order)}
|
29
|
+
@order.process_status_cd(@context)
|
30
|
+
@order.status_key.should == :stock_error
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should recieve action process #0" do
|
34
|
+
g0 = @state.guards[0] # :pay_cash_on_delivery?
|
35
|
+
a0 = g0.action # :reserve_point
|
36
|
+
a0.should_receive(:process).with(@context).and_return{|order| a0.action.process(order)}
|
37
|
+
@order.process_status_cd(@context)
|
38
|
+
@order.status_key.should == :stock_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should recieve action process #1" do
|
42
|
+
g0 = @state.guards[0] # :pay_cash_on_delivery?
|
43
|
+
a0 = g0.action # :reserve_point
|
44
|
+
a1 = a0.action # :reserve_stock
|
45
|
+
a1.should_receive(:process).with(@context).and_return{|order| a1.events[0].process(order)}
|
46
|
+
@order.process_status_cd(@context)
|
47
|
+
@order.status_key.should == :deliver_preparing
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "action event invocation" do
|
51
|
+
before do
|
52
|
+
g0 = @state.guards[0] # :pay_cash_on_delivery?
|
53
|
+
a0 = g0.action # :reserve_point
|
54
|
+
@a1 = a0.action # :reserve_stock
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should recieve action events_for_action" do
|
58
|
+
@order.should_receive(:reserve_stock).and_return(nil)
|
59
|
+
@a1.should_receive(:event_for_action_result).and_return(@a1.events[0])
|
60
|
+
@order.process_status_cd(@context)
|
61
|
+
@order.status_key.should == :deliver_preparing
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should recieve action events_for_action" do
|
65
|
+
@order.should_receive(:reserve_stock).and_return(:reserve_stock_ok)
|
66
|
+
@a1.should_receive(:event_for_action_result).and_return(@a1.events[1])
|
67
|
+
@order.process_status_cd(@context)
|
68
|
+
@order.status_key.should == :stock_error
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should recieve action event update_to_destination" do
|
73
|
+
@order.should_receive(:reserve_stock).and_return(:reserve_stock_ok)
|
74
|
+
@order.process_status_cd(@context)
|
75
|
+
@order.status_key.should == :deliver_preparing
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should recieve action event update_to_destination #2" do
|
79
|
+
@order.should_receive(:reserve_stock).and_return(nil)
|
80
|
+
@order.process_status_cd(@context)
|
81
|
+
@order.status_key.should == :stock_error
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe ":credit_card" do
|
86
|
+
before do
|
87
|
+
StateFlow::Log.delete_all
|
88
|
+
Order.delete_all
|
89
|
+
@order = Order.new
|
90
|
+
@order.product_name = "Beautiful Code"
|
91
|
+
@order.payment_type = :credit_card
|
92
|
+
@order.status_key = :waiting_settling
|
93
|
+
@order.save!
|
94
|
+
Order.count == 1
|
95
|
+
|
96
|
+
@flow = Order.state_flow_for(:status_cd)
|
97
|
+
@state = @flow.origin
|
98
|
+
@state.name.should == :waiting_settling
|
99
|
+
|
100
|
+
@context = @flow.prepare_context(@order)
|
101
|
+
end
|
102
|
+
|
103
|
+
describe ":reserve_stock_ok" do
|
104
|
+
before do
|
105
|
+
@order.should_receive(:reserve_stock).and_return(:reserve_stock_ok)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should recieve guard process" do
|
109
|
+
g1 = @state.guards[1]
|
110
|
+
g1.should_receive(:process).with(@context).and_return{|order| g1.action.process(order)}
|
111
|
+
@order.process_status_cd(@context)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should recieve action event_for_action_result" do
|
115
|
+
g1 = @state.guards[1]
|
116
|
+
a0 = g1.action
|
117
|
+
a1 = a0.action
|
118
|
+
a1.event_for_action_result(:reserve_stock_ok).should == a1.events[0]
|
119
|
+
a1.should_receive(:event_for_action_result).and_return(a1.events[0])
|
120
|
+
@order.process_status_cd(@context)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should recieve evnet process" do
|
124
|
+
g1 = @state.guards[1]
|
125
|
+
a0 = g1.action
|
126
|
+
a1 = a0.action
|
127
|
+
e0 = a1.events[0]
|
128
|
+
e0.should_receive(:process).with(@context).and_return{|order| e0.guards[1].process(order)}
|
129
|
+
@order.process_status_cd(@context)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should recieve evnet process" do
|
133
|
+
g1 = @state.guards[1]
|
134
|
+
a0 = g1.action
|
135
|
+
a1 = a0.action
|
136
|
+
e0 = a1.events[0]
|
137
|
+
g1 = e0.guards[1]
|
138
|
+
g1.should_receive(:process).with(@context).and_return{|order| g1.update_to_destination(order)}
|
139
|
+
@order.process_status_cd(@context)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: state_flow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Takeshi Akima
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-11-04 00:00:00 +09:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -31,21 +31,30 @@ files:
|
|
31
31
|
- install.rb
|
32
32
|
- lib/state_flow.rb
|
33
33
|
- lib/state_flow/action.rb
|
34
|
+
- lib/state_flow/action_client.rb
|
35
|
+
- lib/state_flow/action_event.rb
|
34
36
|
- lib/state_flow/base.rb
|
35
|
-
- lib/state_flow/
|
36
|
-
- lib/state_flow/
|
37
|
+
- lib/state_flow/context.rb
|
38
|
+
- lib/state_flow/element.rb
|
39
|
+
- lib/state_flow/element_visitable.rb
|
37
40
|
- lib/state_flow/event.rb
|
41
|
+
- lib/state_flow/event_client.rb
|
42
|
+
- lib/state_flow/exception_handler.rb
|
43
|
+
- lib/state_flow/exception_handler_client.rb
|
44
|
+
- lib/state_flow/guard.rb
|
45
|
+
- lib/state_flow/guard_client.rb
|
38
46
|
- lib/state_flow/log.rb
|
39
|
-
- lib/state_flow/
|
47
|
+
- lib/state_flow/named_event.rb
|
48
|
+
- lib/state_flow/named_guard.rb
|
49
|
+
- lib/state_flow/state.rb
|
40
50
|
- spec/.gitignore
|
41
|
-
- spec/base_spec.rb
|
42
|
-
- spec/concurrency_spec.rb
|
43
51
|
- spec/database.yml
|
44
|
-
- spec/
|
45
|
-
- spec/resources/models/
|
52
|
+
- spec/order_spec.rb
|
53
|
+
- spec/resources/models/order.rb
|
46
54
|
- spec/schema.rb
|
47
55
|
- spec/spec.opts
|
48
56
|
- spec/spec_helper.rb
|
57
|
+
- spec/state_flow/state_spec.rb
|
49
58
|
- tasks/state_flow_tasks.rake
|
50
59
|
- uninstall.rb
|
51
60
|
has_rdoc: true
|
@@ -77,9 +86,8 @@ signing_key:
|
|
77
86
|
specification_version: 3
|
78
87
|
summary: state_flow provides a DSL for State Transition
|
79
88
|
test_files:
|
80
|
-
- spec/
|
81
|
-
- spec/
|
82
|
-
- spec/page_spec.rb
|
83
|
-
- spec/resources/models/page.rb
|
89
|
+
- spec/order_spec.rb
|
90
|
+
- spec/resources/models/order.rb
|
84
91
|
- spec/schema.rb
|
85
92
|
- spec/spec_helper.rb
|
93
|
+
- spec/state_flow/state_spec.rb
|
data/lib/state_flow/builder.rb
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require 'state_flow'
|
3
|
-
module StateFlow
|
4
|
-
|
5
|
-
module Builder
|
6
|
-
module ClientClassMethods
|
7
|
-
def state_flow_for(selectable_attr)
|
8
|
-
return nil unless @state_flows
|
9
|
-
@state_flows.detect{|flow| flow.attr_name == selectable_attr}
|
10
|
-
end
|
11
|
-
|
12
|
-
def state_flow(selectable_attr, options = nil, &block)
|
13
|
-
options = {
|
14
|
-
:attr_key_name => "#{self.enum_base_name(selectable_attr)}_key".to_sym
|
15
|
-
}.update(options || {})
|
16
|
-
flow = Base.new(self, selectable_attr, options[:attr_key_name])
|
17
|
-
flow.instance_eval(&block)
|
18
|
-
flow.setup_events
|
19
|
-
@state_flows ||= []
|
20
|
-
@state_flows << flow
|
21
|
-
flow
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def new_entry(key)
|
26
|
-
@entry_hash = nil
|
27
|
-
result = Entry.new(self, key)
|
28
|
-
entries << result
|
29
|
-
result
|
30
|
-
end
|
31
|
-
|
32
|
-
def state(*args)
|
33
|
-
raise_invalid_state_argument if args.length > 2
|
34
|
-
if args.length == 2
|
35
|
-
# 引数が2個ならできるだけ一つのHashにまとめます。
|
36
|
-
case args.first
|
37
|
-
when Hash then
|
38
|
-
return state(args.first.update(args.last))
|
39
|
-
when Symbol, String
|
40
|
-
return state({args.first => nil}.update(args.last))
|
41
|
-
else
|
42
|
-
raise_invalid_state_argument
|
43
|
-
end
|
44
|
-
end
|
45
|
-
# 引数が一つだけになってます
|
46
|
-
arg = args.first
|
47
|
-
case arg
|
48
|
-
when Symbol, String
|
49
|
-
return state({args.first => nil})
|
50
|
-
when Hash then
|
51
|
-
# through
|
52
|
-
else
|
53
|
-
raise_invalid_state_argument
|
54
|
-
end
|
55
|
-
# 引数がHash一つだけの場合
|
56
|
-
base_options = extract_common_options(arg)
|
57
|
-
arg.each do |key, value|
|
58
|
-
entry = new_entry(key)
|
59
|
-
build_entry(entry, value, base_options)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def action(name)
|
64
|
-
NamedAction.new(self, name)
|
65
|
-
end
|
66
|
-
|
67
|
-
def event(name)
|
68
|
-
Event.new(self, name)
|
69
|
-
end
|
70
|
-
|
71
|
-
def setup_events
|
72
|
-
event_defs = {}
|
73
|
-
entries.each do |entry|
|
74
|
-
origin_key = entry.key
|
75
|
-
entry.events.each do |event|
|
76
|
-
event_trans = event_defs[event.name] ||= {}
|
77
|
-
event_trans[origin_key] = [event.success_key, event.failure_key]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
event_defs.each do |event_name, trans|
|
81
|
-
method_def = <<-"EOS"
|
82
|
-
def #{event_name}
|
83
|
-
@state_flow ||= self.class.state_flow_for(:#{attr_name})
|
84
|
-
@#{event_name}_transitions ||= #{trans.inspect}
|
85
|
-
@state_flow.process_with_log(self,
|
86
|
-
*@#{event_name}_transitions[#{attr_key_name}])
|
87
|
-
end
|
88
|
-
EOS
|
89
|
-
if klass.instance_methods.include?(event_name)
|
90
|
-
klass.logger.warn("state_flow plugin was going to define #{event_name} but didn't do it because #{event_name} does exist.")
|
91
|
-
else
|
92
|
-
klass.module_eval(method_def, __FILE__, __LINE__)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
def build_entry(entry, options_or_success_key, base_options)
|
100
|
-
if options_or_success_key.nil?
|
101
|
-
return null_action_entry(entry, base_options)
|
102
|
-
end
|
103
|
-
case options_or_success_key
|
104
|
-
when String, Symbol then
|
105
|
-
return null_action_entry(entry, base_options, options_or_success_key.to_sym)
|
106
|
-
when Action then
|
107
|
-
entry.action = setup_action(options_or_success_key, base_options)
|
108
|
-
return entry
|
109
|
-
when Hash then
|
110
|
-
options = options_or_success_key
|
111
|
-
prior_options = extract_common_options(options)
|
112
|
-
options_keys = options.keys
|
113
|
-
if options_keys.all?{|key| key.is_a?(Action)}
|
114
|
-
build_action(entry, options, base_options.merge(prior_options))
|
115
|
-
elsif options_keys.all?{|key| key.is_a?(Event)}
|
116
|
-
raise_invalid_state_argument unless entry.is_a?(Entry)
|
117
|
-
build_events(entry, options, base_options.merge(prior_options))
|
118
|
-
else
|
119
|
-
raise_invalid_state_argument
|
120
|
-
end
|
121
|
-
when Array then
|
122
|
-
options_or_success_key.each do |options|
|
123
|
-
each_base_options = base_options.merge(extract_common_options(options))
|
124
|
-
build_entry(entry, options, each_base_options)
|
125
|
-
end
|
126
|
-
else
|
127
|
-
raise_invalid_state_argument
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def null_action_entry(entry, options, success_key = nil)
|
132
|
-
action = Action.new(self)
|
133
|
-
options = options.dup
|
134
|
-
entry.action = setup_action(action, options, success_key)
|
135
|
-
entry.options = options
|
136
|
-
entry
|
137
|
-
end
|
138
|
-
|
139
|
-
def build_action(entry, action_hash, options)
|
140
|
-
raise_invalid_state_argument unless action_hash.length == 1
|
141
|
-
options = options.dup
|
142
|
-
entry.action = setup_action(action_hash.keys.first, options, action_hash.values.first)
|
143
|
-
entry.options = options
|
144
|
-
end
|
145
|
-
|
146
|
-
def setup_action(action, options, success_key = nil)
|
147
|
-
action.success_key = success_key.to_sym if success_key
|
148
|
-
action.failure_key = options.delete(:failure)
|
149
|
-
action.lock = options.delete(:lock)
|
150
|
-
action.if = options.delete(:if)
|
151
|
-
action.unless = options.delete(:unless)
|
152
|
-
action
|
153
|
-
end
|
154
|
-
|
155
|
-
|
156
|
-
def build_events(entry, event_hash, options)
|
157
|
-
event_hash.each do |event, value|
|
158
|
-
build_entry(event, value, options)
|
159
|
-
entry.events << event
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
COMMON_OPTION_NAMES = [:lock, :if, :unless, :failure]
|
164
|
-
|
165
|
-
def extract_common_options(hash)
|
166
|
-
COMMON_OPTION_NAMES.inject({}) do |dest, name|
|
167
|
-
value = hash.delete(name)
|
168
|
-
dest[name] = value if value
|
169
|
-
dest
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def raise_invalid_state_argument
|
174
|
-
raise ArgumentError, state_argument_pattern
|
175
|
-
end
|
176
|
-
|
177
|
-
def state_argument_pattern
|
178
|
-
descriptions = <<-"EOS"
|
179
|
-
state arguments pattern:
|
180
|
-
* state :<state_name>
|
181
|
-
* state :<state_name> => :<new_state_name>
|
182
|
-
* state :<state_name> => action(:<method_name>)
|
183
|
-
* state :<state_name> => { action(:<method_name>) => :<new_state_name>}
|
184
|
-
* state :<state_name> => { event(:<event_name1>) => :<new_state_name>, event(:<event_name2>) => :<new_state_name>}
|
185
|
-
* state :<state_name> => { event(:<event_name1>) => { action(:<method_name1>) => :<new_state_name>}, event(:<event_name2>) => {action(:<method_name1>) => :<new_state_name>} }
|
186
|
-
And you can append :lock, :if, :unless option in Hash
|
187
|
-
EOS
|
188
|
-
descriptions.split(/$/).map{|s| s.sub(/^\s{8}/, '')}.join("\n")
|
189
|
-
end
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
end
|
data/lib/state_flow/entry.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require 'state_flow'
|
3
|
-
module StateFlow
|
4
|
-
|
5
|
-
class Entry
|
6
|
-
include Action::Executable
|
7
|
-
attr_reader :flow, :key
|
8
|
-
|
9
|
-
def initialize(flow, key)
|
10
|
-
@flow = flow
|
11
|
-
@key = key.to_s.to_sym
|
12
|
-
end
|
13
|
-
|
14
|
-
def events
|
15
|
-
@events ||= [];
|
16
|
-
end
|
17
|
-
|
18
|
-
def event_for(name)
|
19
|
-
events.detect{|event| event.name == name}
|
20
|
-
end
|
21
|
-
|
22
|
-
def process(&block)
|
23
|
-
value = flow.state_cd_by_key(key)
|
24
|
-
find_options = {
|
25
|
-
:order => "id asc",
|
26
|
-
:conditions => ["#{flow.attr_name} = ?", value]
|
27
|
-
}
|
28
|
-
find_options[:lock] = action.lock if action.lock
|
29
|
-
if record = flow.klass.find(:first, find_options)
|
30
|
-
action.process(record, &block) if action
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def inspect
|
35
|
-
result = "<#{self.class.name} @key=#{@key.inspect}"
|
36
|
-
result << " @action=#{@action.inspect}" if @action
|
37
|
-
if @events && !@events.empty?
|
38
|
-
result << " @events=#{@events.sort_by{|event|event.name.to_s}.inspect}"
|
39
|
-
end
|
40
|
-
result << ">"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require 'state_flow'
|
3
|
-
module StateFlow
|
4
|
-
|
5
|
-
class NamedAction < Action
|
6
|
-
attr_reader :name
|
7
|
-
def initialize(flow, name)
|
8
|
-
super(flow)
|
9
|
-
@name = name.to_s.to_sym
|
10
|
-
end
|
11
|
-
|
12
|
-
def proceed
|
13
|
-
flow.process_with_log(self.record, success_key, failure_key) do
|
14
|
-
self.record.send(name)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|