call_center 0.0.2
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/.document +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +52 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +159 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/call_center.gemspec +97 -0
- data/init.rb +1 -0
- data/lib/call_center.rb +100 -0
- data/lib/call_center/core_ext/object_instance_exec.rb +21 -0
- data/lib/call_center/state_machine_ext.rb +26 -0
- data/lib/call_center/test/dsl.rb +69 -0
- data/test/call_center_test.rb +261 -0
- data/test/core_ext_test.rb +18 -0
- data/test/examples/call.rb +46 -0
- data/test/examples/legacy_call.rb +26 -0
- data/test/examples/multiple_flow_call.rb +21 -0
- data/test/examples/non_standard_call.rb +12 -0
- data/test/helper.rb +40 -0
- metadata +278 -0
data/.document
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ree-1.8.7-2011.03@twilio_flow
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem 'shoulda', '>= 0'
|
5
|
+
gem 'bundler', '~> 1.0.0'
|
6
|
+
gem 'jeweler', '~> 1.6.4'
|
7
|
+
gem 'rcov', '>= 0'
|
8
|
+
gem 'test-unit', :require => 'test/unit'
|
9
|
+
gem 'guard'
|
10
|
+
gem 'guard-test'
|
11
|
+
gem 'actionpack', '~> 2.3.10'
|
12
|
+
gem 'mocha'
|
13
|
+
gem 'bourne'
|
14
|
+
gem 'pre-commit'
|
15
|
+
end
|
16
|
+
|
17
|
+
gem 'builder'
|
18
|
+
gem 'hsume2-state_machine', '~> 1.0.5', :require => 'state_machine'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
actionpack (2.3.12)
|
5
|
+
activesupport (= 2.3.12)
|
6
|
+
rack (~> 1.1.0)
|
7
|
+
activesupport (2.3.12)
|
8
|
+
bourne (1.0)
|
9
|
+
mocha (= 0.9.8)
|
10
|
+
builder (3.0.0)
|
11
|
+
execjs (1.2.0)
|
12
|
+
multi_json (~> 1.0)
|
13
|
+
git (1.2.5)
|
14
|
+
guard (0.7.0)
|
15
|
+
thor (~> 0.14.6)
|
16
|
+
guard-test (0.3.0)
|
17
|
+
guard (>= 0.2.2)
|
18
|
+
test-unit (~> 2.2)
|
19
|
+
hsume2-state_machine (1.0.5)
|
20
|
+
jeweler (1.6.4)
|
21
|
+
bundler (~> 1.0)
|
22
|
+
git (>= 1.2.5)
|
23
|
+
rake
|
24
|
+
mocha (0.9.8)
|
25
|
+
rake
|
26
|
+
multi_json (1.0.3)
|
27
|
+
pre-commit (0.1.13)
|
28
|
+
execjs
|
29
|
+
rack (1.1.2)
|
30
|
+
rake (0.9.2)
|
31
|
+
rcov (0.9.9)
|
32
|
+
shoulda (2.11.3)
|
33
|
+
test-unit (2.3.0)
|
34
|
+
thor (0.14.6)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
actionpack (~> 2.3.10)
|
41
|
+
bourne
|
42
|
+
builder
|
43
|
+
bundler (~> 1.0.0)
|
44
|
+
guard
|
45
|
+
guard-test
|
46
|
+
hsume2-state_machine (~> 1.0.5)
|
47
|
+
jeweler (~> 1.6.4)
|
48
|
+
mocha
|
49
|
+
pre-commit
|
50
|
+
rcov
|
51
|
+
shoulda
|
52
|
+
test-unit
|
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Henry Hsu and Zendesk, Inc
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
Call Center
|
2
|
+
===========
|
3
|
+
|
4
|
+
Support for defining call center workflows.
|
5
|
+
|
6
|
+
Overview
|
7
|
+
--------
|
8
|
+
Call Center streamlines the process of defining multi-party call workflows in your application. Particularly, with [Twilio](http://www.twilio.com/docs) in mind.
|
9
|
+
|
10
|
+
[Twilio](http://www.twilio.com/docs) provides a two-part API for managing phone calls, and is mostly driven by callbacks. Call Center DRYs up the application logic dealing with a callback driven API so you can focus on the business logic of your call center.
|
11
|
+
|
12
|
+
### Not DRY
|
13
|
+
Twilio requests your application to return [TwiML](http://www.twilio.com/docs/api/twiml/) that describes the call workflow. TwiML contains commands which Twilio then executes. It is essentially an application-to-application API, synonymous to making a REST call.
|
14
|
+
|
15
|
+
In the context of "[Skinny Controller, Fat Model](http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model)", outgoing REST calls for the function of business logic are not a view concern but a model concern. Therefore, so is TwiML.
|
16
|
+
|
17
|
+
Twilio supports callbacks URLs and redirects to URLs that also render TwiML as a method of modifying live calls. Incoming callbacks are handled by the controller first, but the response is still a model concern.
|
18
|
+
|
19
|
+
Terminology
|
20
|
+
-----------
|
21
|
+
|
22
|
+
* **Call** - An application resource of yours that encapsulates a phone call. Phone calls are then acted on: answered, transferred, declined, etc.
|
23
|
+
* **Event** - Is something that happens outside or inside your application in relation to a **Call**. Someone picks up, hangs up, presses a button, etc.
|
24
|
+
* **State** - Is the status a **Call** is in which is descriptive of what's happened so far and what are the next things that should happen. (e.g. a call on hold is waiting for the agent to return)
|
25
|
+
* **CallFlow** - Is a definition of the process a **Call** goes through. **Events** drive the flow between **States**. (e.g. a simple workflow is when noone answers the call, send the call to voicemail)
|
26
|
+
* **Render** - Is the ability of the **CallFlow** to return TwiML to bring the call into the **State** or modify the live call through a **Redirect**.
|
27
|
+
* **Redirect** - Is a way of modifying a live call outside of a TwiML response (e.g. background jobs)
|
28
|
+
|
29
|
+
Usage
|
30
|
+
-----
|
31
|
+
|
32
|
+
class Call
|
33
|
+
include CallCenter
|
34
|
+
|
35
|
+
call_flow :state, :intial => :answered do
|
36
|
+
state :answered do
|
37
|
+
event :incoming_call, :to => :voicemail, :if => :not_during_business_hours?
|
38
|
+
event :incoming_call, :to => :sales
|
39
|
+
end
|
40
|
+
|
41
|
+
state :voicemail do
|
42
|
+
event :customer_hangs_up, :to => :voicemail_completed
|
43
|
+
end
|
44
|
+
|
45
|
+
on_render(:sales) do |call, x|
|
46
|
+
x.Say "This is Sales!"
|
47
|
+
end
|
48
|
+
|
49
|
+
on_render(:voicemail) do |call, x|
|
50
|
+
x.Say "Leave a voicemail!"
|
51
|
+
end
|
52
|
+
|
53
|
+
on_flow_to(:voicemail) do |call, transition|
|
54
|
+
call.notify(:voicemail)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Benefits of **CallCenter** is that it's backed by [state_machine](https://github.com/pluginaweek/state_machine). Which means you can interact with events the same you do in state_machine.
|
60
|
+
|
61
|
+
@call.incoming_call!
|
62
|
+
@call.voicemail?
|
63
|
+
@call.sales?
|
64
|
+
@call.render # See Rendering
|
65
|
+
|
66
|
+
Flow
|
67
|
+
----
|
68
|
+
|
69
|
+
The general application flow for a **CallFlow** is like this:
|
70
|
+
|
71
|
+
1. An incoming call is posted to your application
|
72
|
+
* You create a **Call**
|
73
|
+
* You execute an initial event
|
74
|
+
* You respond by rendering TwiML. Your TwiML contains callbacks to events or redirects
|
75
|
+
2. Something happens and Twilio posts an event to your application
|
76
|
+
* You find the **Call**
|
77
|
+
* You store any new information
|
78
|
+
* You execute the posted event
|
79
|
+
* You respond by rendering TwiML. Your TwiML contains callbacks to events or redirects
|
80
|
+
3. Repeat 2.
|
81
|
+
|
82
|
+
Rendering
|
83
|
+
---------
|
84
|
+
|
85
|
+
Rendering is your way of interacting with Twilio. Thus, it provides two facilities: access to an XML builder and access to your call.
|
86
|
+
|
87
|
+
on_render(:sales) do |the_call, xml_builder|
|
88
|
+
xml_builder.Say "This is Sales!"
|
89
|
+
|
90
|
+
the_call.flag! # You can access the call explicitly
|
91
|
+
flag! # Or access it implicitly
|
92
|
+
end
|
93
|
+
|
94
|
+
Renders:
|
95
|
+
|
96
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
97
|
+
<Response>
|
98
|
+
<Say>This is Sales!</Say>
|
99
|
+
</Response>
|
100
|
+
|
101
|
+
Callbacks
|
102
|
+
---------
|
103
|
+
|
104
|
+
If you ever want to do something special after entering a state, but only if it's a new transition (e.g. NOT from :voicemail => :voicemail), you can do this:
|
105
|
+
|
106
|
+
on_flow_to(:voicemail) do |the_call, the_transition|
|
107
|
+
the_call.notify(transition.event) # Explicitly
|
108
|
+
notify(transition.event) # Implicitly
|
109
|
+
end
|
110
|
+
|
111
|
+
Redirects
|
112
|
+
---------
|
113
|
+
|
114
|
+
Redirects are a request made to the [Twilio REST API](http://www.twilio.com/docs/api/rest/) that points to a callback URL which returns TwiML to be executed on a call. It is up to you how you want to perform this (e.g. with your favority http libraries, or with [Twilio Libraries](http://www.twilio.com/docs/libraries/)).
|
115
|
+
|
116
|
+
Redirect to events look like this:
|
117
|
+
|
118
|
+
...
|
119
|
+
call_flow :state, :intial => :answered do
|
120
|
+
state :answered do
|
121
|
+
...
|
122
|
+
end
|
123
|
+
|
124
|
+
state :ending_call do
|
125
|
+
event :end_call, :to => :ended_call
|
126
|
+
end
|
127
|
+
|
128
|
+
on_render(:ending_call) do
|
129
|
+
redirect_and_end_call!(:status => 'completed')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
...
|
133
|
+
|
134
|
+
For your **Call** to support this syntax, it must adhere to the following API:
|
135
|
+
|
136
|
+
class Call
|
137
|
+
def redirect_to(event, *args)
|
138
|
+
# where:
|
139
|
+
# event #=> :end_call
|
140
|
+
# args #=> [:status => 'completed]
|
141
|
+
@account.calls.get(self.sid).update({:url => "http://myapp.com/call_flow?event=#{event}"})
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
Tools
|
146
|
+
-----
|
147
|
+
|
148
|
+
### Drawing ###
|
149
|
+
|
150
|
+
Should you be interested in what your call center workflow looks like, you can draw.
|
151
|
+
|
152
|
+
Call.state_machines[:status].draw(:font => 'Helvetica Neue')
|
153
|
+
# OR
|
154
|
+
@call.draw_call_flow(:font => 'Helvetica Neue')
|
155
|
+
|
156
|
+
Future
|
157
|
+
------
|
158
|
+
|
159
|
+
* Integrate making new calls into the **CallFlow** DSL
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "call_center"
|
18
|
+
gem.homepage = "http://github.com/zendesk/call_center"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Support for describing call center workflows}
|
21
|
+
gem.description = %Q{Support for describing call center workflows}
|
22
|
+
gem.email = "hhsu@zendesk.com"
|
23
|
+
gem.authors = ["Henry Hsu"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/*_test.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "twilio_flow #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
data/call_center.gemspec
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{call_center}
|
8
|
+
s.version = "0.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Henry Hsu"]
|
12
|
+
s.date = %q{2011-09-21}
|
13
|
+
s.description = %q{Support for describing call center workflows}
|
14
|
+
s.email = %q{hhsu@zendesk.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rvmrc",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"Guardfile",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.md",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"call_center.gemspec",
|
30
|
+
"init.rb",
|
31
|
+
"lib/call_center.rb",
|
32
|
+
"lib/call_center/core_ext/object_instance_exec.rb",
|
33
|
+
"lib/call_center/state_machine_ext.rb",
|
34
|
+
"lib/call_center/test/dsl.rb",
|
35
|
+
"test/call_center_test.rb",
|
36
|
+
"test/core_ext_test.rb",
|
37
|
+
"test/examples/call.rb",
|
38
|
+
"test/examples/legacy_call.rb",
|
39
|
+
"test/examples/multiple_flow_call.rb",
|
40
|
+
"test/examples/non_standard_call.rb",
|
41
|
+
"test/helper.rb"
|
42
|
+
]
|
43
|
+
s.homepage = %q{http://github.com/zendesk/call_center}
|
44
|
+
s.licenses = ["MIT"]
|
45
|
+
s.require_paths = ["lib"]
|
46
|
+
s.rubygems_version = %q{1.5.3}
|
47
|
+
s.summary = %q{Support for describing call center workflows}
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<builder>, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<hsume2-state_machine>, ["~> 1.0.5"])
|
55
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
58
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<test-unit>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<guard>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<guard-test>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<actionpack>, ["~> 2.3.10"])
|
63
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<bourne>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<pre-commit>, [">= 0"])
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<builder>, [">= 0"])
|
68
|
+
s.add_dependency(%q<hsume2-state_machine>, ["~> 1.0.5"])
|
69
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
70
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
71
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
72
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
73
|
+
s.add_dependency(%q<test-unit>, [">= 0"])
|
74
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
75
|
+
s.add_dependency(%q<guard-test>, [">= 0"])
|
76
|
+
s.add_dependency(%q<actionpack>, ["~> 2.3.10"])
|
77
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
78
|
+
s.add_dependency(%q<bourne>, [">= 0"])
|
79
|
+
s.add_dependency(%q<pre-commit>, [">= 0"])
|
80
|
+
end
|
81
|
+
else
|
82
|
+
s.add_dependency(%q<builder>, [">= 0"])
|
83
|
+
s.add_dependency(%q<hsume2-state_machine>, ["~> 1.0.5"])
|
84
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
85
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
86
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
87
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
88
|
+
s.add_dependency(%q<test-unit>, [">= 0"])
|
89
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
90
|
+
s.add_dependency(%q<guard-test>, [">= 0"])
|
91
|
+
s.add_dependency(%q<actionpack>, ["~> 2.3.10"])
|
92
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
93
|
+
s.add_dependency(%q<bourne>, [">= 0"])
|
94
|
+
s.add_dependency(%q<pre-commit>, [">= 0"])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'call_center'
|
data/lib/call_center.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'call_center/core_ext/object_instance_exec'
|
2
|
+
require 'state_machine'
|
3
|
+
require 'call_center/state_machine_ext'
|
4
|
+
|
5
|
+
module CallCenter
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :cached_state_machines
|
13
|
+
end
|
14
|
+
self.cached_state_machines ||= {}
|
15
|
+
|
16
|
+
def self.cache(klass, state_machine)
|
17
|
+
self.cached_state_machines["#{klass.name}_#{state_machine.name}"] ||= state_machine
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.cached(klass, state_machine_name)
|
21
|
+
self.cached_state_machines["#{klass.name}_#{state_machine_name}"]
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
attr_accessor :call_flow_state_machine_name
|
26
|
+
|
27
|
+
# Calls state_machine ... with :syntax => :alternate
|
28
|
+
def call_flow(*args, &blk)
|
29
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
30
|
+
args << options.merge(:syntax => :alternate)
|
31
|
+
state_machine_name = args.first || :state
|
32
|
+
if state_machine = CallCenter.cached(self, state_machine_name)
|
33
|
+
state_machine = state_machine.duplicate_to(self)
|
34
|
+
else
|
35
|
+
state_machine = state_machine(*args, &blk)
|
36
|
+
state_machine.instance_eval do
|
37
|
+
after_transition any => any do |call, transition|
|
38
|
+
call.flow_to(transition) if transition.from_name != transition.to_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
CallCenter.cache(self, state_machine)
|
42
|
+
end
|
43
|
+
self.call_flow_state_machine_name ||= state_machine.name
|
44
|
+
state_machine
|
45
|
+
end
|
46
|
+
|
47
|
+
def current_state_machine
|
48
|
+
self.state_machines[self.call_flow_state_machine_name]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module InstanceMethods
|
53
|
+
def render(state_machine_name = self.class.call_flow_state_machine_name)
|
54
|
+
xml = Builder::XmlMarkup.new
|
55
|
+
render_block = current_block_accessor(:render_blocks, state_machine_name)
|
56
|
+
|
57
|
+
xml.instruct!
|
58
|
+
xml.Response do
|
59
|
+
self.instance_exec(self, xml, &render_block) if render_block
|
60
|
+
end
|
61
|
+
xml.target!
|
62
|
+
end
|
63
|
+
|
64
|
+
def flow_to(transition, state_machine_name = self.class.call_flow_state_machine_name)
|
65
|
+
block = current_block_accessor(:flow_to_blocks, state_machine_name)
|
66
|
+
self.instance_exec(self, transition, &block) if block
|
67
|
+
end
|
68
|
+
|
69
|
+
def draw_call_flow(*args)
|
70
|
+
current_state_machine.draw(*args)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def current_block_accessor(accessor, state_machine_name)
|
76
|
+
csm = self.class.state_machines[state_machine_name]
|
77
|
+
return unless csm.respond_to?(accessor)
|
78
|
+
blocks, name = csm.send(accessor), csm.name
|
79
|
+
blocks[current_flow_state(state_machine_name)] if blocks
|
80
|
+
end
|
81
|
+
|
82
|
+
def current_state_machine
|
83
|
+
self.class.current_state_machine
|
84
|
+
end
|
85
|
+
|
86
|
+
def current_flow_state(state_machine_name)
|
87
|
+
send(state_machine_name).to_sym
|
88
|
+
end
|
89
|
+
|
90
|
+
def method_missing(*args, &blk)
|
91
|
+
method_name = args.first.to_s
|
92
|
+
if method_name =~ /^redirect_and_(.+)!$/
|
93
|
+
args.shift
|
94
|
+
redirect_to($1.to_sym, *args)
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|