state_flow 0.1.0 → 0.2.0
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.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
|