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
data/README.rdoc
CHANGED
@@ -3,38 +3,76 @@
|
|
3
3
|
|
4
4
|
状態遷移のためのDSLを提供するためのActiveRecordを拡張するプラグインです。
|
5
5
|
|
6
|
+
基本的にUMLのステートチャート図に忠実に状態遷移を記述できるようにすることが最終目標です。
|
7
|
+
現在のところ、以下のような機能があります。
|
8
|
+
* イベント/ガード/アクションの設定
|
9
|
+
* イベントとして定義可能なのは、以下の通りです。
|
10
|
+
* モデルに対する任意の名前のアクション(メソッドとして定義されるので、メソッド名に被っていると上書きされます)。
|
11
|
+
* アクションの戻り値
|
12
|
+
* 例外
|
13
|
+
* ガードはその条件に該当するかどうかを判断するメソッド名を指定します。
|
14
|
+
* アクションはモデルのメソッドを指定しますが、状態を変更するコードは不要です。
|
15
|
+
* 状態の遷移は自動で行われます。
|
16
|
+
* 状態のネスト
|
17
|
+
* 親の状態で定義されたイベントが実行された場合に正しく状態を遷移します。
|
18
|
+
* 状態を遷移する際のトランザクションの制御
|
19
|
+
* 各状態間の遷移毎にトランザクションを発行します。
|
20
|
+
* 例外発生時などにはロールバックを行い、(指定されていれば)例外に対応する状態に遷移してモデルを保存します。
|
21
|
+
|
22
|
+
=== サンプル
|
6
23
|
以下のような記述が可能です。
|
7
24
|
|
8
|
-
class
|
9
|
-
|
25
|
+
class Order < ActiveRecord::Base
|
26
|
+
|
27
|
+
class StockShortageError < StandardError
|
28
|
+
end
|
10
29
|
|
11
30
|
selectable_attr :status_cd do
|
12
|
-
entry '
|
13
|
-
entry '
|
14
|
-
entry '
|
15
|
-
entry '
|
16
|
-
entry '07', :published , '公開済'
|
17
|
-
entry '08', :publish_failure, '公開失敗'
|
31
|
+
entry '00', :waiting_settling , '決済前'
|
32
|
+
entry '01', :online_settling , '決済中'
|
33
|
+
entry '02', :receiving , '入金待ち'
|
34
|
+
entry '03', :deliver_preparing, '配送準備中'
|
18
35
|
end
|
19
36
|
|
20
37
|
state_flow(:status_cd) do
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
38
|
+
origin(:waiting_settling)
|
39
|
+
|
40
|
+
group(:valid) do
|
41
|
+
from(:waiting_settling) do
|
42
|
+
guard(:pay_cash_on_delivery?).action(:reserve_point).action(:reserve_stock){
|
43
|
+
event(:reserve_stock_ok).to(:deliver_preparing)
|
44
|
+
event_else.action(:delete_point).to(:stock_error)
|
45
|
+
}
|
46
|
+
guard_else.action(:reserve_point).action(:reserve_stock, :temporary => true){
|
47
|
+
event(:reserve_stock_ok){
|
48
|
+
guard(:bank_deposit?).action(:send_mail_thanks).to(:receiving)
|
49
|
+
guard(:credit_card?).to(:online_settling)
|
50
|
+
guard(:foreign_payment?).action(:settle).to(:online_settling)
|
51
|
+
}
|
52
|
+
event_else{
|
53
|
+
guard(:foreign_payment?).action(:delete_point).action(:send_mail_stock_shortage)
|
54
|
+
}.to(:stock_error)
|
55
|
+
}
|
56
|
+
recover(StockShortageError).to(:stock_error)
|
57
|
+
end
|
58
|
+
|
59
|
+
from(:online_settling) do
|
60
|
+
guard(:credit_card?).action(:settle){
|
61
|
+
event(:ok).action(:reserve_stock).action(:send_mail_thanks).to(:deliver_preparing)
|
62
|
+
event_else.action(:release_stock).action(:delete_point).to(:settlement_error)
|
63
|
+
}
|
64
|
+
guard(:foreign_payment?){
|
65
|
+
event(:settlement_ok).to(:deliver_preparing)
|
66
|
+
event(:settlement_ng).action(:release_stock).action(:delete_point).action(:send_mail_invalid_purchage).to(:settlement_error)
|
67
|
+
}
|
68
|
+
recover(Exception).action(:release_stock).action(:delete_point).to(:settlement_error)
|
69
|
+
end
|
28
70
|
end
|
29
|
-
|
30
|
-
state :published
|
31
|
-
end
|
32
|
-
|
33
|
-
def start_publish
|
34
|
-
# 公開時の処理
|
35
71
|
end
|
36
72
|
end
|
37
73
|
|
74
|
+
詳しくはspec/order_spec.rbをご覧ください。
|
75
|
+
|
38
76
|
== セットアップ
|
39
77
|
state_flowプラグインはselectable_attrに依存しています。
|
40
78
|
|
@@ -78,8 +116,8 @@ config/initializers/state_flow.rb
|
|
78
116
|
|
79
117
|
== Example
|
80
118
|
以下のテスト用のモデルや、テストをご覧ください。
|
81
|
-
http://github.com/akm/state_flow/blob/master/spec/resources/models/
|
82
|
-
http://github.com/akm/state_flow/blob/master/spec/
|
119
|
+
http://github.com/akm/state_flow/blob/master/spec/resources/models/order.rb
|
120
|
+
http://github.com/akm/state_flow/blob/master/spec/order_spec.rb
|
83
121
|
|
84
122
|
|
85
123
|
Copyright (c) 2009 [Takeshi AKIMA], released under the MIT license
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/state_flow/action.rb
CHANGED
@@ -2,66 +2,32 @@
|
|
2
2
|
require 'state_flow'
|
3
3
|
module StateFlow
|
4
4
|
|
5
|
-
class Action
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_accessor :lock, :if, :unless
|
5
|
+
class Action < Element
|
6
|
+
include ActionClient
|
7
|
+
include EventClient
|
8
|
+
include GuardClient
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
|
15
|
-
|
16
|
-
def record
|
17
|
-
Thread.current[@record_key_on_thread]
|
18
|
-
end
|
19
|
-
|
20
|
-
def record=(value)
|
21
|
-
Thread.current[@record_key_on_thread] = value
|
22
|
-
end
|
23
|
-
|
24
|
-
def process(record)
|
25
|
-
return if self.if && !call_or_send(self.if, record)
|
26
|
-
return if self.unless && call_or_send(self.unless, record)
|
27
|
-
self.record = record
|
28
|
-
begin
|
29
|
-
block_given? ? yield(self) : proceed
|
30
|
-
ensure
|
31
|
-
self.record = nil
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def proceed
|
36
|
-
flow.process_with_log(self.record, success_key, failure_key)
|
10
|
+
attr_reader :method_name, :method_args
|
11
|
+
def initialize(origin, method_name, *method_args, &block)
|
12
|
+
@method_name, @method_args = method_name, method_args
|
13
|
+
super(origin, &block)
|
37
14
|
end
|
38
15
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
result << '>'
|
53
|
-
end
|
54
|
-
|
55
|
-
module Executable
|
56
|
-
attr_accessor :action
|
57
|
-
|
58
|
-
def options ; @options ||= {} ; end
|
59
|
-
def options=(value); @options = value; end
|
60
|
-
|
61
|
-
def success_key; action.success_key if action; end
|
62
|
-
def failure_key; action.failure_key if action; end
|
16
|
+
def process(context)
|
17
|
+
context.trace(self)
|
18
|
+
context.mark_proceeding
|
19
|
+
exception_handling(context) do
|
20
|
+
result = context.record_send(method_name, *method_args)
|
21
|
+
event = event_for_action_result(result)
|
22
|
+
if event
|
23
|
+
event.process(context)
|
24
|
+
elsif action
|
25
|
+
action.process(context)
|
26
|
+
end
|
27
|
+
update_to_destination(context)
|
28
|
+
end
|
63
29
|
end
|
64
30
|
|
65
31
|
end
|
66
|
-
|
32
|
+
|
67
33
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'state_flow'
|
3
|
+
module StateFlow
|
4
|
+
|
5
|
+
module ActionClient
|
6
|
+
def action(method_name = nil, *method_args, &block)
|
7
|
+
if method_name
|
8
|
+
result = Action.new(self, method_name, *method_args, &block)
|
9
|
+
@action = result
|
10
|
+
result
|
11
|
+
else
|
12
|
+
@action
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'state_flow'
|
3
|
+
module StateFlow
|
4
|
+
|
5
|
+
class ActionEvent < Event
|
6
|
+
ELSE = Object.new
|
7
|
+
|
8
|
+
attr_reader :matcher
|
9
|
+
def initialize(origin, matcher, &block)
|
10
|
+
@matcher = matcher
|
11
|
+
super(origin, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def match?(action_result)
|
15
|
+
return true if matcher == ActionEvent::ELSE
|
16
|
+
matcher === action_result
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/state_flow/base.rb
CHANGED
@@ -3,28 +3,32 @@ require 'state_flow'
|
|
3
3
|
module StateFlow
|
4
4
|
|
5
5
|
class Base
|
6
|
-
include Builder
|
7
6
|
|
8
|
-
module
|
7
|
+
module ClientClassMethods
|
8
|
+
def state_flow_for(selectable_attr)
|
9
|
+
return nil unless @state_flows
|
10
|
+
@state_flows.detect{|flow| flow.attr_name == selectable_attr}
|
11
|
+
end
|
9
12
|
|
10
|
-
def
|
13
|
+
def state_flow(selectable_attr, options = nil, &block)
|
11
14
|
options = {
|
12
|
-
:
|
13
|
-
}.update(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
15
|
+
:attr_key_name => "#{self.enum_base_name(selectable_attr)}_key".to_sym
|
16
|
+
}.update(options || {})
|
17
|
+
flow = Base.new(self, selectable_attr, options[:attr_key_name])
|
18
|
+
flow.instance_eval(&block)
|
19
|
+
@state_flows ||= []
|
20
|
+
@state_flows << flow
|
21
|
+
module_eval(<<-EOS, __FILE__, __LINE__)
|
22
|
+
def process_#{selectable_attr}(context_or_options = nil)
|
23
|
+
flow = self.class.state_flow_for(:#{selectable_attr})
|
24
|
+
context = flow.prepare_context(self, context_or_options)
|
25
|
+
context.process(flow)
|
24
26
|
end
|
25
|
-
|
27
|
+
EOS
|
28
|
+
NamedEvent.build_event_methods(flow)
|
29
|
+
flow
|
26
30
|
end
|
27
|
-
|
31
|
+
|
28
32
|
def transaction_if_need(with_transaction, &block)
|
29
33
|
if with_transaction
|
30
34
|
self.transaction(&block)
|
@@ -33,30 +37,70 @@ module StateFlow
|
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
36
|
-
|
40
|
+
|
37
41
|
attr_reader :klass, :attr_name, :attr_key_name, :status_keys
|
38
|
-
attr_reader :entries
|
39
42
|
def initialize(klass, attr_name, attr_key_name)
|
40
43
|
@klass, @attr_name, @attr_key_name = klass, attr_name, attr_key_name
|
41
44
|
@status_keys = klass.send(@attr_key_name.to_s.pluralize).map{|s| s.to_sym}
|
42
|
-
@entries = []
|
43
45
|
end
|
44
46
|
|
45
47
|
def state_cd_by_key(key)
|
46
48
|
@state_cd_by_key_method_name ||= "#{klass.enum_base_name(attr_name)}_id_by_key"
|
47
49
|
klass.send(@state_cd_by_key_method_name, key)
|
48
50
|
end
|
49
|
-
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
|
52
|
+
def state(name, &block)
|
53
|
+
result = State.new(self, name, &block)
|
54
|
+
states << result
|
55
|
+
result
|
56
|
+
end
|
57
|
+
alias_method :group, :state
|
58
|
+
alias_method :state_group, :state
|
59
|
+
|
60
|
+
def states
|
61
|
+
@states ||= []
|
62
|
+
end
|
63
|
+
|
64
|
+
def all_states
|
65
|
+
unless @all_states
|
66
|
+
@all_states = states.map{|state| state.descendants}.flatten.inject({}) do |dest, state|
|
67
|
+
dest[state.name] = state
|
54
68
|
dest
|
55
69
|
end
|
56
70
|
end
|
57
|
-
@
|
71
|
+
@all_states
|
72
|
+
end
|
73
|
+
|
74
|
+
def concrete_states
|
75
|
+
unless @concrete_states
|
76
|
+
@concrete_states = {}
|
77
|
+
all_states.each do |name, state|
|
78
|
+
@concrete_states[state.name] = state if state.concrete?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
@concrete_states
|
82
|
+
end
|
83
|
+
|
84
|
+
def origin(value = nil)
|
85
|
+
if value
|
86
|
+
@origin_name = value
|
87
|
+
else
|
88
|
+
@origin ||= all_states[@origin_name]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def prepare_context(record, context_or_options = nil)
|
93
|
+
context_or_options.is_a?(StateFlow::Context) ?
|
94
|
+
context_or_options :
|
95
|
+
StateFlow::Context.new(self, record, context_or_options)
|
96
|
+
end
|
97
|
+
|
98
|
+
def process(context)
|
99
|
+
current_key = context.current_attr_key
|
100
|
+
state = concrete_states[current_key]
|
101
|
+
state.process(context)
|
102
|
+
context
|
58
103
|
end
|
59
|
-
alias_method :[], :entry_for
|
60
104
|
|
61
105
|
def process_with_log(record, success_key, failure_key)
|
62
106
|
origin_state = record.send(attr_name)
|
@@ -99,7 +143,6 @@ module StateFlow
|
|
99
143
|
def inspect
|
100
144
|
result = "<#{self.class.name} @attr_name=#{@attr_name.inspect} @attr_key_name=#{@attr_key_name.inspect}"
|
101
145
|
result << " @klass=\"#{@klass.name}\""
|
102
|
-
result << " @entries=#{@entries.inspect}"
|
103
146
|
result << '>'
|
104
147
|
end
|
105
148
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module StateFlow
|
2
|
+
|
3
|
+
class Context
|
4
|
+
|
5
|
+
attr_reader :flow, :record, :options
|
6
|
+
|
7
|
+
def initialize(flow, record, options = nil)
|
8
|
+
@flow, @record = flow, record
|
9
|
+
@options = {
|
10
|
+
:save => :save!,
|
11
|
+
:keep_process => true
|
12
|
+
}.update(options || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def process(flow_or_named_event = flow)
|
16
|
+
flow.klass.transaction do
|
17
|
+
flow_or_named_event.process(self)
|
18
|
+
save_record_if_need
|
19
|
+
end
|
20
|
+
if options[:keep_process]
|
21
|
+
last_current_key = current_attr_key
|
22
|
+
while true
|
23
|
+
@mark_proceeding = false
|
24
|
+
flow.process(self)
|
25
|
+
save_record_if_need if @mark_proceeding
|
26
|
+
break unless @mark_proceeding
|
27
|
+
break if last_current_key == current_attr_key
|
28
|
+
last_current_key = current_attr_key
|
29
|
+
end
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def mark_proceeding
|
35
|
+
@mark_proceeding = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def trace(object)
|
39
|
+
stack_trace << object
|
40
|
+
end
|
41
|
+
|
42
|
+
def stack_trace
|
43
|
+
@stack_trace ||= []
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def save_record_if_need
|
48
|
+
return unless options[:save]
|
49
|
+
record.send(options[:save])
|
50
|
+
end
|
51
|
+
|
52
|
+
def record_send(*args, &block)
|
53
|
+
record.send(*args, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def record_reload_if_possible
|
57
|
+
record.reload unless record.new_record?
|
58
|
+
end
|
59
|
+
|
60
|
+
def transaction_rollback
|
61
|
+
record.class.connection.rollback_db_transaction
|
62
|
+
end
|
63
|
+
|
64
|
+
def exceptions
|
65
|
+
@exceptions ||= []
|
66
|
+
end
|
67
|
+
|
68
|
+
def recovered_exceptions
|
69
|
+
@recovered_exceptions ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
def recovered?(exception)
|
73
|
+
recovered_exceptions.include?(exception)
|
74
|
+
end
|
75
|
+
|
76
|
+
def current_attr_key
|
77
|
+
record_send(flow.attr_key_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'state_flow'
|
3
|
+
module StateFlow
|
4
|
+
|
5
|
+
class Element
|
6
|
+
include ElementVisitable
|
7
|
+
|
8
|
+
attr_reader :origin
|
9
|
+
attr_reader :destination
|
10
|
+
def initialize(origin, &block)
|
11
|
+
@origin = origin
|
12
|
+
instance_eval(&block) if block
|
13
|
+
end
|
14
|
+
|
15
|
+
def to(destination)
|
16
|
+
@destination = destination
|
17
|
+
end
|
18
|
+
|
19
|
+
def flow
|
20
|
+
@flow || origin.flow
|
21
|
+
end
|
22
|
+
|
23
|
+
def state
|
24
|
+
origin.is_a?(State) ? origin : origin.state
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_to_destination(context)
|
28
|
+
return unless destination
|
29
|
+
context.mark_proceeding
|
30
|
+
context.record_send("#{flow.attr_key_name}=", destination)
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def uninspected_var(*vars)
|
35
|
+
@@uninspected_var_hash ||= {}
|
36
|
+
@@uninspected_var_hash[self] ||= []
|
37
|
+
@@uninspected_var_hash[self].concat(vars.map{|v| v.to_s.sub(/^([^@])/){"@#{$1}"}})
|
38
|
+
end
|
39
|
+
|
40
|
+
def uninspected_vars
|
41
|
+
@uninspected_vars ||= @@uninspected_var_hash.nil? ? %w(@flow @origin) :
|
42
|
+
self.ancestors.map{|klass| @@uninspected_var_hash[klass] || []}.flatten
|
43
|
+
end
|
44
|
+
end
|
45
|
+
self.uninspected_var :flow, :origin, :events, :guards, :action
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
vars = (instance_variables - self.class.uninspected_vars).map do |name|
|
49
|
+
"#{name}=#{instance_variable_get(name).inspect}"
|
50
|
+
end
|
51
|
+
# vars = []
|
52
|
+
vars.unshift("%s:%#x" % [self.class.name, self.object_id])
|
53
|
+
"#<#{vars.join(' ')}>"
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'state_flow'
|
3
|
+
module StateFlow
|
4
|
+
|
5
|
+
module ElementVisitable
|
6
|
+
# Visitorパターン
|
7
|
+
def visit(&block)
|
8
|
+
results = block.call(self)
|
9
|
+
(results || [:events, :guards, :action]).each do |elements_name|
|
10
|
+
next if [:events, :guards, :action].include?(elements_name) && !respond_to?(elements_name)
|
11
|
+
elements = send(elements_name)
|
12
|
+
elements = [elements] unless elements.is_a?(Array)
|
13
|
+
elements.each do |element|
|
14
|
+
element.visit(&block) if element
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/state_flow/event.rb
CHANGED
@@ -2,19 +2,19 @@
|
|
2
2
|
require 'state_flow'
|
3
3
|
module StateFlow
|
4
4
|
|
5
|
-
class Event
|
6
|
-
include
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
5
|
+
class Event < Element
|
6
|
+
include GuardClient
|
7
|
+
include ActionClient
|
8
|
+
include ExceptionHandlerClient
|
9
|
+
|
10
|
+
def process(context)
|
11
|
+
context.trace(self)
|
12
|
+
if guard = guard_for(context)
|
13
|
+
guard.process(context)
|
14
|
+
else
|
15
|
+
action.process(context) if action
|
16
|
+
end
|
17
|
+
update_to_destination(context)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'state_flow'
|
3
|
+
module StateFlow
|
4
|
+
|
5
|
+
module EventClient
|
6
|
+
include ExceptionHandlerClient
|
7
|
+
|
8
|
+
def events
|
9
|
+
@events ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def event(*name_or_matcher_or_exceptions, &block)
|
13
|
+
options = name_or_matcher_or_exceptions.extract_options!
|
14
|
+
if name_or_matcher_or_exceptions.all?{|v| v.is_a?(Class) && v <= Exception}
|
15
|
+
handle_exception(*name_or_matcher_or_exceptions.push(options), &block)
|
16
|
+
else
|
17
|
+
if name_or_matcher_or_exceptions.length > 1
|
18
|
+
raise ArgumentError, "event(event_name) or event(action_result) or event(Exception1, Exception2...)"
|
19
|
+
end
|
20
|
+
name_or_matcher = name_or_matcher_or_exceptions.first
|
21
|
+
if origin.is_a?(State)
|
22
|
+
named_event(name_or_matcher, &block)
|
23
|
+
else
|
24
|
+
action_event(name_or_matcher, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def named_event(name, &block)
|
30
|
+
result = NamedEvent.new(self, name, &block)
|
31
|
+
events << result
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def action_event(matcher, &block)
|
36
|
+
result = ActionEvent.new(self, matcher, &block)
|
37
|
+
events << result
|
38
|
+
result
|
39
|
+
end
|
40
|
+
|
41
|
+
def event_else(&block)
|
42
|
+
if origin.is_a?(State)
|
43
|
+
raise ArgumentError, "event_else can't be after/in a state but an action"
|
44
|
+
end
|
45
|
+
result = ActionEvent.new(self, ActionEvent::ELSE, &block)
|
46
|
+
events << result
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_exception(*args, &block)
|
51
|
+
result = ExceptionHandler.new(self, *args, &block)
|
52
|
+
events << result
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def recover(*args, &block)
|
57
|
+
options = {
|
58
|
+
:recovering => true,
|
59
|
+
:rolling_back => true,
|
60
|
+
:logging => :error
|
61
|
+
}.update(args.extract_options!)
|
62
|
+
handle_exception(*(args << options), &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def event_for_action_result(result)
|
66
|
+
events.detect{|ev| ev.match?(result)}
|
67
|
+
end
|
68
|
+
|
69
|
+
def exception_handling(context)
|
70
|
+
begin
|
71
|
+
yield
|
72
|
+
rescue Exception => exception
|
73
|
+
context.exceptions << exception
|
74
|
+
context.trace(exception)
|
75
|
+
handlers = events.select{|ev| ev.is_a?(ExceptionHandler)}
|
76
|
+
handlers.each do |handler|
|
77
|
+
next unless handler.match?(exception)
|
78
|
+
handler.process(context)
|
79
|
+
break
|
80
|
+
end
|
81
|
+
raise exception unless context.recovered?(exception)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
end
|