stealth 0.9.8 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|