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.
@@ -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.1.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-10-27 00:00:00 +09:00
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/builder.rb
36
- - lib/state_flow/entry.rb
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/named_action.rb
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/page_spec.rb
45
- - spec/resources/models/page.rb
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/base_spec.rb
81
- - spec/concurrency_spec.rb
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
@@ -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
@@ -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