stealth 0.9.8 → 0.10.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -3
- data/VERSION +1 -1
- data/lib/stealth/base.rb +3 -1
- data/lib/stealth/controller/callbacks.rb +64 -0
- data/lib/stealth/controller/catch_all.rb +54 -0
- data/lib/stealth/{controller.rb → controller/controller.rb} +28 -20
- data/lib/stealth/errors.rb +6 -0
- data/lib/stealth/flow/base.rb +17 -211
- data/lib/stealth/flow/core_ext.rb +11 -0
- data/lib/stealth/flow/specification.rb +12 -45
- data/lib/stealth/flow/state.rb +49 -19
- data/lib/stealth/session.rb +50 -7
- data/spec/configuration_spec.rb +2 -2
- data/spec/controller/callbacks_spec.rb +225 -0
- data/spec/controller/state_transitions_spec.rb +108 -0
- data/spec/flow/flow_spec.rb +13 -43
- data/spec/flow/state_spec.rb +71 -0
- data/spec/session_spec.rb +116 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/sample_messages.rb +65 -0
- data/spec/{sample_services_yml → support}/services.yml +0 -0
- data/spec/{sample_services_yml → support}/services_with_erb.yml +0 -0
- data/stealth.gemspec +1 -0
- metadata +35 -15
- data/lib/stealth/flow/errors.rb +0 -25
- data/lib/stealth/flow/event.rb +0 -43
- data/lib/stealth/flow/event_collection.rb +0 -41
- data/spec/flow/custom_transitions_spec.rb +0 -99
- data/spec/flow/transition_callbacks_spec.rb +0 -228
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05a79205c1c753dc6ab5b49f1c23695cf13b7d5c
|
4
|
+
data.tar.gz: bff5e9b18f8b6fb5608f23834f65e2526460f440
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a0997a30591438682bc14fd7c5a61f45935cc0780572bef8a0bd3fc92e2d0beb7d88dd730dd3391088457a953150485b0c6670ade82dc58c869e621af43e1f3
|
7
|
+
data.tar.gz: 68973f3f68bd5c9c8f0b85405fe45f7f0d55e5b8f3a3aa4467d21fef2c6429ff715d144196ac934b4e1a00b76de90d0d48433b370be17914672723c1a62dcca3
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stealth (0.
|
4
|
+
stealth (0.10.0)
|
5
5
|
activesupport (~> 5.1)
|
6
6
|
multi_json (~> 1.12)
|
7
7
|
puma (~> 3.10)
|
@@ -23,10 +23,11 @@ GEM
|
|
23
23
|
i18n (0.9.1)
|
24
24
|
concurrent-ruby (~> 1.0)
|
25
25
|
minitest (5.10.3)
|
26
|
+
mock_redis (0.17.3)
|
26
27
|
multi_json (1.12.2)
|
27
28
|
mustermann (1.0.1)
|
28
29
|
oj (3.3.6)
|
29
|
-
puma (3.
|
30
|
+
puma (3.11.0)
|
30
31
|
rack (2.0.3)
|
31
32
|
rack-protection (2.0.0)
|
32
33
|
rack
|
@@ -68,6 +69,7 @@ PLATFORMS
|
|
68
69
|
ruby
|
69
70
|
|
70
71
|
DEPENDENCIES
|
72
|
+
mock_redis (~> 0.17)
|
71
73
|
oj (~> 3.3)
|
72
74
|
rack-test (~> 0.7)
|
73
75
|
rspec (~> 3.6)
|
@@ -75,4 +77,4 @@ DEPENDENCIES
|
|
75
77
|
stealth!
|
76
78
|
|
77
79
|
BUNDLED WITH
|
78
|
-
1.
|
80
|
+
1.16.1
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.0
|
data/lib/stealth/base.rb
CHANGED
@@ -19,7 +19,9 @@ require 'stealth/scheduled_reply'
|
|
19
19
|
require 'stealth/service_reply'
|
20
20
|
require 'stealth/service_message'
|
21
21
|
require 'stealth/session'
|
22
|
-
require 'stealth/controller'
|
22
|
+
require 'stealth/controller/callbacks'
|
23
|
+
require 'stealth/controller/catch_all'
|
24
|
+
require 'stealth/controller/controller'
|
23
25
|
require 'stealth/flow/base'
|
24
26
|
require 'stealth/services/base_client'
|
25
27
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
class Controller
|
6
|
+
module Callbacks
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
include ActiveSupport::Callbacks
|
11
|
+
|
12
|
+
included do
|
13
|
+
define_callbacks :action, skip_after_callbacks_if_terminated: true
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def _normalize_callback_options(options)
|
18
|
+
_normalize_callback_option(options, :only, :if)
|
19
|
+
_normalize_callback_option(options, :except, :unless)
|
20
|
+
end
|
21
|
+
|
22
|
+
def _normalize_callback_option(options, from, to)
|
23
|
+
if from = options[from]
|
24
|
+
_from = Array(from).map(&:to_s).to_set
|
25
|
+
from = proc { |c| _from.include?(c.action_name) }
|
26
|
+
options[to] = Array(options[to]).unshift(from)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def _insert_callbacks(callbacks, block = nil)
|
31
|
+
options = callbacks.extract_options!
|
32
|
+
_normalize_callback_options(options)
|
33
|
+
callbacks.push(block) if block
|
34
|
+
callbacks.each do |callback|
|
35
|
+
yield callback, options
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
[:before, :after, :around].each do |callback|
|
40
|
+
define_method "#{callback}_action" do |*names, &blk|
|
41
|
+
_insert_callbacks(names, blk) do |name, options|
|
42
|
+
set_callback(:action, callback, name, options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method "prepend_#{callback}_action" do |*names, &blk|
|
47
|
+
_insert_callbacks(names, blk) do |name, options|
|
48
|
+
set_callback(:action, callback, name, options.merge(prepend: true))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
define_method "skip_#{callback}_action" do |*names|
|
53
|
+
_insert_callbacks(names) do |name, options|
|
54
|
+
skip_callback(:action, callback, name, options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method :"append_#{callback}_action", :"#{callback}_action"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
class Controller
|
6
|
+
module CatchAll
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
|
12
|
+
def run_catch_all(reason: nil)
|
13
|
+
error_level = fetch_error_level
|
14
|
+
Stealth::Logger.l(topic: "catch_all", message: "CatchAll #{catch_all_state(error_level)} triggered for #{error_slug}: #{reason}")
|
15
|
+
|
16
|
+
if defined?(CatchAllsController)
|
17
|
+
step_to flow: 'catch_all', state: catch_all_state(error_level)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fetch_error_level
|
24
|
+
if fail_attempts = $redis.get(error_slug)
|
25
|
+
begin
|
26
|
+
fail_attempts = Integer(fail_attempts)
|
27
|
+
rescue ArgumentError
|
28
|
+
fail_attempts = 1
|
29
|
+
end
|
30
|
+
|
31
|
+
fail_attempts += 1
|
32
|
+
else
|
33
|
+
fail_attempts = 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set the error with an expiration to avoid filling Redis
|
37
|
+
$redis.setex(error_slug, 15.minutes.to_i, fail_attempts)
|
38
|
+
|
39
|
+
fail_attempts
|
40
|
+
end
|
41
|
+
|
42
|
+
def error_slug
|
43
|
+
['error', current_user_id, current_session.flow_string, current_session.state_string].join('-')
|
44
|
+
end
|
45
|
+
|
46
|
+
def catch_all_state(error_level)
|
47
|
+
"level#{error_level}"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -4,18 +4,18 @@
|
|
4
4
|
module Stealth
|
5
5
|
class Controller
|
6
6
|
|
7
|
-
include
|
8
|
-
|
9
|
-
define_callbacks :action
|
7
|
+
include Stealth::Controller::Callbacks
|
8
|
+
include Stealth::Controller::CatchAll
|
10
9
|
|
11
10
|
attr_reader :current_message, :current_user_id, :current_flow,
|
12
|
-
:current_service, :flow_controller
|
11
|
+
:current_service, :flow_controller, :action_name
|
13
12
|
|
14
13
|
def initialize(service_message:, current_flow: nil)
|
15
14
|
@current_message = service_message
|
16
15
|
@current_service = service_message.service
|
17
16
|
@current_user_id = service_message.sender_id
|
18
17
|
@current_flow = current_flow
|
18
|
+
@progressed = false
|
19
19
|
end
|
20
20
|
|
21
21
|
def has_location?
|
@@ -26,6 +26,10 @@ module Stealth
|
|
26
26
|
current_message.attachments.present?
|
27
27
|
end
|
28
28
|
|
29
|
+
def progressed?
|
30
|
+
@progressed.present?
|
31
|
+
end
|
32
|
+
|
29
33
|
def route
|
30
34
|
raise(Stealth::Errors::ControllerRoutingNotImplemented, "Please implement `route` method in BotController")
|
31
35
|
end
|
@@ -57,10 +61,12 @@ module Stealth
|
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
64
|
+
|
65
|
+
@progressed = :sent_replies
|
60
66
|
end
|
61
67
|
|
62
68
|
def flow_controller
|
63
|
-
@flow_controller
|
69
|
+
@flow_controller ||= begin
|
64
70
|
flow_controller = [current_session.flow_string.pluralize, 'controller'].join('_').classify.constantize
|
65
71
|
flow_controller.new(
|
66
72
|
service_message: @current_message,
|
@@ -73,10 +79,21 @@ module Stealth
|
|
73
79
|
@current_session ||= Stealth::Session.new(user_id: current_user_id)
|
74
80
|
end
|
75
81
|
|
82
|
+
def previous_session
|
83
|
+
@previous_session ||= Stealth::Session.new(user_id: current_user_id, previous: true)
|
84
|
+
end
|
85
|
+
|
76
86
|
def action(action: nil)
|
87
|
+
@action_name = action
|
88
|
+
@action_name ||= current_session.state_string
|
89
|
+
|
77
90
|
run_callbacks :action do
|
78
|
-
|
79
|
-
|
91
|
+
begin
|
92
|
+
flow_controller.send(@action_name)
|
93
|
+
run_catch_all(reason: 'Did not send replies, update session, or step') unless progressed?
|
94
|
+
rescue StandardError => e
|
95
|
+
run_catch_all(reason: e.message)
|
96
|
+
end
|
80
97
|
end
|
81
98
|
end
|
82
99
|
|
@@ -84,7 +101,7 @@ module Stealth
|
|
84
101
|
flow, state = get_flow_and_state(session: session, flow: flow, state: state)
|
85
102
|
|
86
103
|
unless delay.is_a?(ActiveSupport::Duration)
|
87
|
-
raise ArgumentError, "Please specify your
|
104
|
+
raise ArgumentError, "Please specify your step_to_in `delay` parameter using ActiveSupport::Duration, e.g. `1.day` or `5.hours`"
|
88
105
|
end
|
89
106
|
|
90
107
|
Stealth::ScheduledReplyJob.perform_in(delay, current_service, current_user_id, flow, state)
|
@@ -110,18 +127,6 @@ module Stealth
|
|
110
127
|
update_session(flow: flow, state: state)
|
111
128
|
end
|
112
129
|
|
113
|
-
def self.before_action(*args, &block)
|
114
|
-
set_callback(:action, :before, *args, &block)
|
115
|
-
end
|
116
|
-
|
117
|
-
def self.around_action(*args, &block)
|
118
|
-
set_callback(:action, :around, *args, &block)
|
119
|
-
end
|
120
|
-
|
121
|
-
def self.after_action(*args, &block)
|
122
|
-
set_callback(:action, :after, *args, &block)
|
123
|
-
end
|
124
|
-
|
125
130
|
private
|
126
131
|
|
127
132
|
def reply_handler
|
@@ -156,11 +161,14 @@ module Stealth
|
|
156
161
|
|
157
162
|
def update_session(flow:, state:)
|
158
163
|
@current_session = Stealth::Session.new(user_id: current_user_id)
|
164
|
+
@progressed = :updated_session
|
159
165
|
@current_session.set(flow: flow, state: state)
|
160
166
|
end
|
161
167
|
|
162
168
|
def step(flow:, state:)
|
163
169
|
update_session(flow: flow, state: state)
|
170
|
+
@progressed = :stepped
|
171
|
+
@flow_controller = nil
|
164
172
|
@current_flow = current_session.flow
|
165
173
|
|
166
174
|
action(action: state)
|
data/lib/stealth/errors.rb
CHANGED
data/lib/stealth/flow/base.rb
CHANGED
@@ -1,153 +1,38 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'stealth/flow/
|
4
|
+
require 'stealth/flow/core_ext'
|
5
5
|
require 'stealth/flow/specification'
|
6
|
-
require 'stealth/flow/event_collection'
|
7
|
-
require 'stealth/flow/event'
|
8
6
|
require 'stealth/flow/state'
|
9
7
|
|
10
8
|
module Stealth
|
11
9
|
module Flow
|
12
10
|
|
13
|
-
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
class_methods do
|
14
14
|
attr_reader :flow_spec
|
15
15
|
|
16
16
|
def flow(&specification)
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
# Creates the convinience methods like `my_transition!`
|
23
|
-
def assign_flow(specification_object)
|
24
|
-
|
25
|
-
# Merging two workflow specifications can **not** be done automically, so
|
26
|
-
# just make the latest specification win. Same for inheritance -
|
27
|
-
# definition in the subclass wins.
|
28
|
-
if respond_to? :inherited_flow_spec # undefine methods defined by the old flow_spec
|
29
|
-
inherited_flow_spec.states.values.each do |state|
|
30
|
-
state_name = state.name
|
31
|
-
module_eval do
|
32
|
-
undef_method "#{state_name}?"
|
33
|
-
end
|
34
|
-
|
35
|
-
state.events.flat.each do |event|
|
36
|
-
event_name = event.name
|
37
|
-
module_eval do
|
38
|
-
undef_method "#{event_name}!".to_sym
|
39
|
-
undef_method "can_#{event_name}?"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
@flow_spec = specification_object
|
46
|
-
@flow_spec.states.values.each do |state|
|
47
|
-
state_name = state.name
|
48
|
-
module_eval do
|
49
|
-
define_method "#{state_name}?" do
|
50
|
-
state_name == current_state.name
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
state.events.flat.each do |event|
|
55
|
-
event_name = event.name
|
56
|
-
module_eval do
|
57
|
-
define_method "#{event_name}!".to_sym do |*args|
|
58
|
-
process_event!(event_name, *args)
|
59
|
-
end
|
60
|
-
|
61
|
-
define_method "can_#{event_name}?" do
|
62
|
-
return !!current_state.events.first_applicable(event_name, self)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
17
|
+
@flow_spec = Specification.new(&specification)
|
67
18
|
end
|
68
19
|
end
|
69
20
|
|
70
|
-
|
21
|
+
included do
|
71
22
|
attr_accessor :flow_state, :user_id
|
72
23
|
|
73
24
|
def current_state
|
74
|
-
|
75
|
-
res = spec.states[loaded_state.to_sym] if loaded_state
|
25
|
+
res = spec.states[@flow_state.to_sym] if @flow_state
|
76
26
|
res || spec.initial_state
|
77
27
|
end
|
78
28
|
|
79
|
-
# Return true if the last transition was halted by one of the transition callbacks.
|
80
|
-
def halted?
|
81
|
-
@halted
|
82
|
-
end
|
83
|
-
|
84
|
-
# Return the reason of the last transition abort as set by the previous
|
85
|
-
# call of `halt` or `halt!` method.
|
86
|
-
def halted_because
|
87
|
-
@halted_because
|
88
|
-
end
|
89
|
-
|
90
|
-
def process_event!(name, *args)
|
91
|
-
event = current_state.events.first_applicable(name, self)
|
92
|
-
raise NoTransitionAllowed.new(
|
93
|
-
"There is no event #{name.to_sym} defined for the #{current_state} state") \
|
94
|
-
if event.nil?
|
95
|
-
@halted_because = nil
|
96
|
-
@halted = false
|
97
|
-
|
98
|
-
check_transition(event)
|
99
|
-
|
100
|
-
from = current_state
|
101
|
-
to = spec.states[event.transitions_to]
|
102
|
-
|
103
|
-
run_before_transition(from, to, name, *args)
|
104
|
-
return false if @halted
|
105
|
-
|
106
|
-
begin
|
107
|
-
return_value = run_action(event.action, *args) || run_action_callback(event.name, *args)
|
108
|
-
rescue StandardError => e
|
109
|
-
run_on_error(e, from, to, name, *args)
|
110
|
-
end
|
111
|
-
|
112
|
-
return false if @halted
|
113
|
-
|
114
|
-
run_on_transition(from, to, name, *args)
|
115
|
-
|
116
|
-
run_on_exit(from, to, name, *args)
|
117
|
-
|
118
|
-
transition_value = persist_flow_state(to.to_s)
|
119
|
-
|
120
|
-
run_on_entry(to, from, name, *args)
|
121
|
-
|
122
|
-
run_after_transition(from, to, name, *args)
|
123
|
-
|
124
|
-
return_value.nil? ? transition_value : return_value
|
125
|
-
end
|
126
|
-
|
127
|
-
def halt(reason = nil)
|
128
|
-
@halted_because = reason
|
129
|
-
@halted = true
|
130
|
-
end
|
131
|
-
|
132
|
-
def halt!(reason = nil)
|
133
|
-
@halted_because = reason
|
134
|
-
@halted = true
|
135
|
-
raise TransitionHalted.new(reason)
|
136
|
-
end
|
137
|
-
|
138
29
|
def spec
|
139
30
|
# check the singleton class first
|
140
31
|
class << self
|
141
32
|
return flow_spec if flow_spec
|
142
33
|
end
|
143
34
|
|
144
|
-
|
145
|
-
# using a simple loop instead of class_inheritable_accessor to avoid
|
146
|
-
# dependency on Rails' ActiveSupport
|
147
|
-
until c.flow_spec || !(c.include? Stealth::Flow)
|
148
|
-
c = c.superclass
|
149
|
-
end
|
150
|
-
c.flow_spec
|
35
|
+
self.class.flow_spec
|
151
36
|
end
|
152
37
|
|
153
38
|
def states
|
@@ -155,102 +40,23 @@ module Stealth
|
|
155
40
|
end
|
156
41
|
|
157
42
|
def init_state(state)
|
158
|
-
|
159
|
-
@flow_state = res || spec.initial_state
|
160
|
-
self
|
161
|
-
end
|
43
|
+
raise(ArgumentError, 'No state was specified.') if state.blank?
|
162
44
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# Create a meaningful error message instead of
|
167
|
-
# "undefined method `on_entry' for nil:NilClass"
|
168
|
-
# Reported by Kyle Burton
|
169
|
-
if !spec.states[event.transitions_to]
|
170
|
-
raise StealthFlowError.new("Event[#{event.name}]'s " +
|
171
|
-
"transitions_to[#{event.transitions_to}] is not a declared state")
|
45
|
+
new_state = state.to_sym
|
46
|
+
unless states.include?(new_state)
|
47
|
+
raise(Stealth::Errors::InvalidStateTransition)
|
172
48
|
end
|
173
|
-
|
174
|
-
|
175
|
-
def run_before_transition(from, to, event, *args)
|
176
|
-
instance_exec(from.name, to.name, event, *args, &spec.before_transition_proc) if spec.before_transition_proc
|
177
|
-
end
|
49
|
+
@flow_state = new_state
|
178
50
|
|
179
|
-
|
180
|
-
if spec.on_error_proc
|
181
|
-
instance_exec(error, from.name, to.name, event, *args, &spec.on_error_proc)
|
182
|
-
halt(error.message)
|
183
|
-
else
|
184
|
-
raise error
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def run_on_transition(from, to, event, *args)
|
189
|
-
instance_exec(from.name, to.name, event, *args, &spec.on_transition_proc) if spec.on_transition_proc
|
190
|
-
end
|
191
|
-
|
192
|
-
def run_after_transition(from, to, event, *args)
|
193
|
-
instance_exec(from.name, to.name, event, *args, &spec.after_transition_proc) if spec.after_transition_proc
|
194
|
-
end
|
195
|
-
|
196
|
-
def run_action(action, *args)
|
197
|
-
instance_exec(*args, &action) if action
|
198
|
-
end
|
199
|
-
|
200
|
-
def has_callback?(action)
|
201
|
-
# 1. public callback method or
|
202
|
-
# 2. protected method somewhere in the class hierarchy or
|
203
|
-
# 3. private in the immediate class (parent classes ignored)
|
204
|
-
action = action.to_sym
|
205
|
-
self.respond_to?(action) or
|
206
|
-
self.class.protected_method_defined?(action) or
|
207
|
-
self.private_methods(false).map(&:to_sym).include?(action)
|
208
|
-
end
|
209
|
-
|
210
|
-
def run_action_callback(action_name, *args)
|
211
|
-
action = action_name.to_sym
|
212
|
-
self.send(action, *args) if has_callback?(action)
|
213
|
-
end
|
214
|
-
|
215
|
-
def run_on_entry(state, prior_state, triggering_event, *args)
|
216
|
-
if state.on_entry
|
217
|
-
instance_exec(prior_state.name, triggering_event, *args, &state.on_entry)
|
218
|
-
else
|
219
|
-
hook_name = "on_#{state}_entry"
|
220
|
-
self.send(hook_name, prior_state, triggering_event, *args) if has_callback?(hook_name)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
def run_on_exit(state, new_state, triggering_event, *args)
|
225
|
-
if state
|
226
|
-
if state.on_exit
|
227
|
-
instance_exec(new_state.name, triggering_event, *args, &state.on_exit)
|
228
|
-
else
|
229
|
-
hook_name = "on_#{state}_exit"
|
230
|
-
self.send(hook_name, new_state, triggering_event, *args) if has_callback?(hook_name)
|
231
|
-
end
|
232
|
-
end
|
51
|
+
self
|
233
52
|
end
|
234
53
|
|
235
|
-
|
236
|
-
@flow_state
|
237
|
-
end
|
54
|
+
private
|
238
55
|
|
239
|
-
|
240
|
-
|
241
|
-
if defined?($redis)
|
242
|
-
$redis.set(user_id, flow_and_state)
|
56
|
+
def flow_and_state
|
57
|
+
[self.class.to_s, current_state].join("->")
|
243
58
|
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def flow_and_state
|
247
|
-
[self.class.to_s, current_state].join("->")
|
248
|
-
end
|
249
59
|
end
|
250
60
|
|
251
|
-
def self.included(klass)
|
252
|
-
klass.send :include, InstanceMethods
|
253
|
-
klass.extend ClassMethods
|
254
|
-
end
|
255
61
|
end
|
256
62
|
end
|
@@ -4,12 +4,10 @@
|
|
4
4
|
module Stealth
|
5
5
|
module Flow
|
6
6
|
class Specification
|
7
|
-
attr_accessor :states, :initial_state
|
8
|
-
:on_transition_proc, :before_transition_proc, :after_transition_proc, :on_error_proc
|
7
|
+
attr_accessor :states, :initial_state
|
9
8
|
|
10
|
-
def initialize(
|
9
|
+
def initialize(&specification)
|
11
10
|
@states = Hash.new
|
12
|
-
@meta = meta
|
13
11
|
instance_eval(&specification)
|
14
12
|
end
|
15
13
|
|
@@ -19,49 +17,18 @@ module Stealth
|
|
19
17
|
|
20
18
|
private
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@scoped_state = new_state
|
28
|
-
instance_eval(&events_and_etc) if events_and_etc
|
29
|
-
end
|
30
|
-
|
31
|
-
def event(name, args = {}, &action)
|
32
|
-
target = args[:transitions_to] || args[:transition_to]
|
33
|
-
condition = args[:if]
|
34
|
-
raise StealthFlowDefinitionError.new(
|
35
|
-
"missing ':transitions_to' in workflow event definition for '#{name}'") \
|
36
|
-
if target.nil?
|
37
|
-
@scoped_state.events.push(
|
38
|
-
name, Stealth::Flow::Event.new(name, target, condition, (args[:meta] or {}), &action)
|
39
|
-
)
|
40
|
-
end
|
20
|
+
def state(name, fails_to: nil)
|
21
|
+
fail_state = nil
|
22
|
+
if fails_to.present?
|
23
|
+
fail_state = Stealth::Flow::State.new(fails_to, self)
|
24
|
+
end
|
41
25
|
|
42
|
-
|
43
|
-
|
44
|
-
|
26
|
+
new_state = Stealth::Flow::State.new(name, self, fail_state)
|
27
|
+
@initial_state = new_state if @states.empty?
|
28
|
+
@states[name.to_sym] = new_state
|
29
|
+
@scoped_state = new_state
|
30
|
+
end
|
45
31
|
|
46
|
-
def on_exit(&proc)
|
47
|
-
@scoped_state.on_exit = proc
|
48
|
-
end
|
49
|
-
|
50
|
-
def after_transition(&proc)
|
51
|
-
@after_transition_proc = proc
|
52
|
-
end
|
53
|
-
|
54
|
-
def before_transition(&proc)
|
55
|
-
@before_transition_proc = proc
|
56
|
-
end
|
57
|
-
|
58
|
-
def on_transition(&proc)
|
59
|
-
@on_transition_proc = proc
|
60
|
-
end
|
61
|
-
|
62
|
-
def on_error(&proc)
|
63
|
-
@on_error_proc = proc
|
64
|
-
end
|
65
32
|
end
|
66
33
|
end
|
67
34
|
end
|