call_center 0.0.3 → 0.0.4

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.md CHANGED
@@ -9,58 +9,64 @@ Call Center streamlines the process of defining multi-party call workflows in yo
9
9
 
10
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
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. But overall, it's anything that can be triggered by Twilio callbacks.
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
12
  Usage
30
13
  -----
31
14
 
32
15
  class Call
33
16
  include CallCenter
34
17
 
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
18
+ call_flow :state, :intial => :incoming do
19
+ actor :customer do |call, event|
20
+ "/voice/calls/flow?event=#{event}&actor=customer&call_id=#{call.id}"
39
21
  end
40
22
 
41
- state :voicemail do
42
- event :customer_hangs_up, :to => :voicemail_completed
23
+ state :incoming do
24
+ response do |x|
25
+ x.Gather :numDigits => '1', :action => customer(:wants_voicemail) do
26
+ x.Say "Hello World"
27
+ x.Play some_nice_music, :loop => 100
28
+ end
29
+ # <?xml version="1.0" encoding="UTF-8" ?>
30
+ # <Response>
31
+ # <Gather numDigits="1" action="/voice/calls/flow?event=wants_voicemail&actor=customer&call_id=5000">
32
+ # <Say>Hello World</Say>
33
+ # <Play loop="100">http://some.nice.music.com/1.mp3</Play>
34
+ # </Gather>
35
+ # </Response>
36
+ end
37
+
38
+ event :called, :to => :routing, :if => :agents_available?
39
+ event :called, :to => :voicemail
40
+ event :wants_voicemail, :to => :voicemail
41
+ event :customer_hangs_up, :to => :cancelled
43
42
  end
44
43
 
45
- on_render(:sales) do |call, x|
46
- x.Say "This is Sales!"
44
+ state :voicemail do
45
+ response do |x|
46
+ x.Say "Please leave a message"
47
+ x.Record(:action => customer(:voicemail_complete))
48
+ # <?xml version="1.0" encoding="UTF-8" ?>
49
+ # <Response>
50
+ # <Say>Please leave a message</Say>
51
+ # <Record action="/voice/calls/flow?event=voicemail_complete&actor=customer&call_id=5000"/>
52
+ # </Response>
53
+ end
54
+
55
+ event :voicemail_complete, :to => :voicemail_completed
56
+ event :customer_hangs_up, :to => :cancelled
47
57
  end
48
58
 
49
- on_render(:voicemail) do |call, x|
50
- x.Say "Leave a voicemail!"
51
- end
59
+ state :routing do
52
60
 
53
- on_flow_to(:voicemail) do |call, transition|
54
- call.notify(:voicemail)
55
61
  end
56
62
  end
57
63
  end
58
64
 
59
65
  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
66
 
61
- @call.incoming_call!
62
- @call.voicemail?
63
- @call.sales?
67
+ @call.called!
68
+ @call.wants_voicemail!
69
+ @call.routing?
64
70
  @call.render # See Rendering
65
71
 
66
72
  Flow
@@ -96,63 +102,69 @@ Rendering
96
102
 
97
103
  Rendering is your way of interacting with Twilio. Thus, it provides two facilities: access to an XML builder and access to your call.
98
104
 
99
- on_render(:sales) do |the_call, xml_builder|
100
- xml_builder.Say "This is Sales!"
101
-
102
- the_call.flag! # You can access the call explicitly
103
- flag! # Or access it implicitly
105
+ state :sales do
106
+ response do |xml_builder, the_call|
107
+ xml_builder.Say "This is #{the_call.agent.name}!" # Or agent.name, you can access he call implicitly
108
+ end
104
109
  end
105
110
 
106
111
  Renders with `@call.render` if the current state is :sales:
107
112
 
108
113
  <?xml version="1.0" encoding="UTF-8"?>
109
114
  <Response>
110
- <Say>This is Sales!</Say>
115
+ <Say>This is Henry!</Say>
111
116
  </Response>
112
117
 
113
118
  Callbacks
114
119
  ---------
115
120
 
116
- 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:
121
+ You have control over what you want to happen before/after state transitions:
117
122
 
118
- on_flow_to(:voicemail) do |the_call, the_transition|
119
- the_call.notify(transition.event) # Explicitly
120
- notify(transition.event) # Implicitly
121
- end
123
+ state :voicemail do
124
+ before(:always) { # Invokes before any transition }
125
+ before(:always, :uniq => true) { # Invokes before transitions to a different state }
122
126
 
123
- Redirects
124
- ---------
127
+ after(:always) { # Invokes after any transition }
128
+ after(:success) { # Invokes after any successful transition }
129
+ after(:failure) { # Invokes after any failed transition (those not covered in your call flow) }
125
130
 
126
- 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/)).
131
+ after(:always, :uniq => true) { # Invokes after any transition to a different state }
132
+ after(:success, :uniq => true) { # Successful unique transitions }
133
+ after(:failure, :uniq => true) { # Failed unique transitions }
134
+ end
127
135
 
128
- Redirect to events look like this:
136
+ For example,
129
137
 
130
- ...
131
- call_flow :state, :intial => :answered do
132
- state :answered do
133
- ...
134
- end
138
+ state :voicemail do
139
+ before(:always) { log_start_event }
135
140
 
136
- state :ending_call do
137
- event :end_call, :to => :ended_call
138
- end
141
+ after(:always) { log_end_event }
142
+ after(:failure) { notify_airbrake }
139
143
 
140
- on_render(:ending_call) do
141
- redirect_and_end_call!(:status => 'completed')
142
- end
144
+ after(:success, :uniq => true) { notify_browser }
145
+ after(:failure, :uniq => true) { notify_cleanup_browser }
143
146
  end
144
- ...
145
147
 
146
- For your **Call** to support this syntax, it must adhere to the following API:
148
+ Motivation
149
+ ----------
147
150
 
148
- class Call
149
- def redirect_to(event, *args)
150
- # where:
151
- # event #=> :end_call
152
- # args #=> [:status => 'completed]
153
- @account.calls.get(self.sid).update({:url => "http://myapp.com/call_flow?event=#{event}"})
154
- end
155
- end
151
+ ### Not DRY
152
+ 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.
153
+
154
+ 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.
155
+
156
+ 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.
157
+
158
+
159
+ Terminology
160
+ -----------
161
+
162
+ * **Call** - An application resource of yours that encapsulates a phone call. Phone calls are then acted on: answered, transferred, declined, etc.
163
+ * **Event** - Is something that happens outside or inside your application in relation to a **Call**. Someone picks up, hangs up, presses a button, etc. But overall, it's anything that can be triggered by Twilio callbacks.
164
+ * **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)
165
+ * **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)
166
+ * **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**.
167
+ * **Redirect** - Is a way of modifying a live call outside of a TwiML response (e.g. background jobs)
156
168
 
157
169
  Tools
158
170
  -----
data/Rakefile CHANGED
@@ -37,7 +37,7 @@ Rcov::RcovTask.new do |test|
37
37
  test.libs << 'test'
38
38
  test.pattern = 'test/**/*_test.rb'
39
39
  test.verbose = true
40
- test.rcov_opts << '--exclude "gems/*"'
40
+ test.rcov_opts << '--exclude "gems/*,lib/call_center/core_ext/object_instance_exec.rb"'
41
41
  end
42
42
 
43
43
  task :default => :test
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
data/call_center.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{call_center}
8
- s.version = "0.0.3"
8
+ s.version = "0.0.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Henry Hsu"]
12
- s.date = %q{2011-09-29}
12
+ s.date = %q{2011-10-23}
13
13
  s.description = %q{Support for describing call center workflows}
14
14
  s.email = %q{hhsu@zendesk.com}
15
15
  s.extra_rdoc_files = [
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
30
30
  "init.rb",
31
31
  "lib/call_center.rb",
32
32
  "lib/call_center/core_ext/object_instance_exec.rb",
33
+ "lib/call_center/flow_callback.rb",
33
34
  "lib/call_center/state_machine_ext.rb",
34
35
  "lib/call_center/test/dsl.rb",
35
36
  "test/call_center_test.rb",
data/lib/call_center.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'call_center/core_ext/object_instance_exec'
2
2
  require 'state_machine'
3
3
  require 'call_center/state_machine_ext'
4
+ require 'call_center/flow_callback'
4
5
 
5
6
  module CallCenter
6
7
  def self.included(base)
@@ -21,6 +22,15 @@ module CallCenter
21
22
  self.cached_state_machines["#{klass.name}_#{state_machine_name}"]
22
23
  end
23
24
 
25
+ def self.render_twiml
26
+ xml = Builder::XmlMarkup.new
27
+ xml.instruct!
28
+ xml.Response do
29
+ yield(xml)
30
+ end
31
+ xml.target!
32
+ end
33
+
24
34
  module ClassMethods
25
35
  attr_accessor :call_flow_state_machine_name
26
36
 
@@ -32,12 +42,7 @@ module CallCenter
32
42
  if state_machine = CallCenter.cached(self, state_machine_name)
33
43
  state_machine = state_machine.duplicate_to(self)
34
44
  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
45
+ state_machine = state_machine(*args, &blk).setup_call_flow(self)
41
46
  CallCenter.cache(self, state_machine)
42
47
  end
43
48
  self.call_flow_state_machine_name ||= state_machine.name
@@ -50,40 +55,27 @@ module CallCenter
50
55
  end
51
56
 
52
57
  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
58
+ def render(name = nil)
59
+ name ||= self.class.call_flow_state_machine_name
60
+ return unless name
61
+ CallCenter.render_twiml do |xml|
62
+ if render_block = state_machine_for_name(name).block_accessor(:response_blocks, current_state(name))
63
+ render_block.arity == 2 ? self.instance_exec(xml, self, &render_block) : self.instance_exec(xml, &render_block)
64
+ end
60
65
  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
66
  end
68
67
 
69
68
  def draw_call_flow(*args)
70
- current_state_machine.draw(*args)
69
+ self.class.current_state_machine.draw(*args)
71
70
  end
72
71
 
73
72
  private
74
73
 
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
74
+ def state_machine_for_name(state_machine_name)
75
+ self.class.state_machines[state_machine_name]
84
76
  end
85
77
 
86
- def current_flow_state(state_machine_name)
78
+ def current_state(state_machine_name)
87
79
  send(state_machine_name).to_sym
88
80
  end
89
81
 
@@ -0,0 +1,69 @@
1
+ module CallCenter
2
+ class FlowCallback
3
+ attr_reader :state_name, :scope, :block
4
+
5
+ def self.create(state_name, scope, options, block)
6
+ case scope
7
+ when :success
8
+ FlowCallback.new(state_name, scope, options, block).extend(SuccessFlowCallback)
9
+ when :failure
10
+ FlowCallback.new(state_name, scope, options, block).extend(FailureFlowCallback)
11
+ else
12
+ FlowCallback.new(state_name, scope, options, block).extend(AlwaysFlowCallback)
13
+ end
14
+ end
15
+
16
+ def initialize(state_name, scope, options, block)
17
+ raise "Invalid scope: #{scope} for flow callback" unless [:always, :success, :failure].include?(scope)
18
+ @state_name, @scope, @block = state_name, scope, block
19
+ extend(UniqueFlowCallback) if options[:uniq]
20
+ end
21
+
22
+ def run(flow, transition)
23
+ @transition = transition
24
+ flow.instance_exec(transition, &block) if should_run?
25
+ end
26
+
27
+ def setup(context)
28
+ callback = self
29
+ context.before_transition(transition_parameters(context)) do |call, transition|
30
+ callback.run(call, transition)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def transition_parameters(context)
37
+ { context.any => @state_name }
38
+ end
39
+
40
+ def should_run?
41
+ true
42
+ end
43
+ end
44
+
45
+ module AlwaysFlowCallback
46
+ def success; true; end
47
+ def failure; true; end
48
+ end
49
+
50
+ module SuccessFlowCallback
51
+ def success; true; end
52
+ def failure; false; end
53
+ end
54
+
55
+ module FailureFlowCallback
56
+ def success; false; end
57
+ def failure; true; end
58
+ end
59
+
60
+ module UniqueFlowCallback
61
+ def should_run?
62
+ !@transition.loopback?
63
+ end
64
+
65
+ def transition_parameters(context)
66
+ { context.any - @state_name => @state_name }
67
+ end
68
+ end
69
+ end
@@ -1,26 +1,98 @@
1
1
  # Extension for StateMachine::Machine to store and provide render blocks
2
2
  class StateMachine::Machine
3
- attr_accessor :render_blocks
4
- attr_accessor :flow_to_blocks
3
+ attr_accessor :response_blocks
4
+ attr_accessor :before_blocks
5
+ attr_accessor :after_blocks
6
+ attr_accessor :flow_actor_blocks
5
7
 
6
- def on_render(state_name, &blk)
7
- @render_blocks ||= {}
8
- @render_blocks[state_name] = blk
8
+ def response(state_name, &blk)
9
+ @response_blocks ||= {}
10
+ @response_blocks[state_name] = blk
9
11
  end
10
12
 
11
- def on_flow_to(state_name, &blk)
12
- @flow_to_blocks ||= {}
13
- @flow_to_blocks[state_name] = blk
13
+ def before(state_name, scope, options, &blk)
14
+ @before_blocks ||= []
15
+ @before_blocks << CallCenter::FlowCallback.create(state_name, :always, options, blk)
16
+ end
17
+
18
+ def after(state_name, scope, options, &blk)
19
+ @after_blocks ||= []
20
+ @after_blocks << CallCenter::FlowCallback.create(state_name, scope, options, blk)
21
+ end
22
+
23
+ def block_accessor(accessor, for_state)
24
+ return unless respond_to?(accessor)
25
+ blocks = send(accessor)
26
+ blocks[for_state] if blocks
27
+ end
28
+
29
+ def flow_actors(name, &blk)
30
+ @flow_actor_blocks ||= {}
31
+ @flow_actor_blocks[name] = blk
32
+ end
33
+
34
+ def setup_call_flow(flow)
35
+ setup_before_blocks
36
+ setup_after_blocks
37
+ setup_flow_actor_blocks(flow)
38
+ self
39
+ end
40
+
41
+ def setup_before_blocks
42
+ return unless @before_blocks
43
+ @before_blocks.each { |callback| callback.setup(self) }
44
+ end
45
+
46
+ def setup_after_blocks
47
+ return unless @after_blocks
48
+ @after_blocks.select(&:success).each { |callback| callback.setup(self) }
49
+
50
+ event_names = events.map(&:name)
51
+ event_names.each do |event_name|
52
+ after_failure :on => event_name do |call, transition|
53
+ callbacks = @after_blocks.select { |callback| callback.state_name == transition.to_name && callback.failure } || []
54
+ callbacks.each { |callback| callback.run(call, transition) }
55
+ end
56
+ end
57
+ end
58
+
59
+ def setup_flow_actor_blocks(flow_class)
60
+ return unless @flow_actor_blocks
61
+ @flow_actor_blocks.each do |actor, block|
62
+ flow_class.send(:define_method, actor) do |event|
63
+ self.instance_exec(self, event, &block) if block
64
+ end
65
+ end
14
66
  end
15
67
  end
16
68
 
17
69
  # Extension for StateMachine::AlternateMachine to provide render blocks inside a state definition
18
70
  class StateMachine::AlternateMachine
19
- def on_render(state_name = nil, &blk)
71
+ def response(state_name = nil, &blk)
20
72
  if @from_state
21
- @queued_sends << [[:on_render, @from_state], blk]
73
+ @queued_sends << [[:response, @from_state], blk]
22
74
  else
23
- @queued_sends << [[:on_render, state_name], blk]
75
+ @queued_sends << [[:response, state_name], blk]
76
+ end
77
+ end
78
+
79
+ def before(scope, options = {}, &blk)
80
+ if @from_state
81
+ @queued_sends << [[:before, @from_state, scope, options], blk]
82
+ end
83
+ end
84
+
85
+ def after(scope, options = {}, &blk)
86
+ if @from_state
87
+ @queued_sends << [[:after, @from_state, scope, options], blk]
88
+ end
89
+ end
90
+
91
+ def actor(name, &blk)
92
+ (class << self; self; end).send(:define_method, name.to_sym) do |event_name, options|
93
+ event_name = :"#{name}_#{event_name}"
94
+ event(event_name, options)
24
95
  end
96
+ @queued_sends << [[:flow_actors, name], blk]
25
97
  end
26
98
  end
@@ -16,7 +16,6 @@ class CallCenterTest < Test::Unit::TestCase
16
16
  klass = call_type.to_s.gsub('_', ' ').titleize.gsub(' ', '').constantize
17
17
  @call = klass.new
18
18
  @call.stubs(:notify)
19
- @call.stubs(:flow_url).returns('the_flow')
20
19
  end
21
20
 
22
21
  context "agents available" do
@@ -99,21 +98,28 @@ class CallCenterTest < Test::Unit::TestCase
99
98
  @call = Call.new
100
99
  end
101
100
 
102
- should "render xml for initial state" do
101
+ should "render xml for initial state" do
103
102
  @call.expects(:notify).with(:rendering_initial)
104
103
  body @call.render
105
104
  assert_select "Response>Say", "Hello World"
106
105
  end
107
106
 
108
- should "render xml for voicemail state" do
107
+ should "return customer url for event" do
108
+ assert_equal("/voice/calls/flow?event=voicemail_complete&actor=customer", @call.customer(:voicemail_complete))
109
+ end
110
+
111
+ should "return agent url for event" do
112
+ assert_equal("/voice/calls/flow?event=voicemail_complete&actor=agent", @call.agent(:voicemail_complete))
113
+ end
114
+
115
+ should "render xml for voicemail state" do
109
116
  @call.stubs(:agents_available?).returns(false)
110
117
  @call.incoming_call!
111
118
  @call.expects(:notify).with(:rendering_voicemail)
112
- @call.expects(:flow_url).with(:voicemail_complete).returns('the_flow')
113
119
 
114
120
  body @call.render
115
121
  assert_select "Response>Say"
116
- assert_select "Response>Record[action=the_flow]"
122
+ assert_select "Response>Record[action=/voice/calls/flow?event=voicemail_complete&actor=customer]"
117
123
  end
118
124
 
119
125
  should "render noop when no render block" do
@@ -124,15 +130,24 @@ class CallCenterTest < Test::Unit::TestCase
124
130
  assert_select "Response"
125
131
  end
126
132
 
127
- should "respond when flow to state (once)" do
128
- @call.state = 'routing'
129
- @call.expects(:notify).with(:cancelled).once
130
- @call.customer_hangs_up!
131
- assert @call.cancelled?
132
- @call.customer_hangs_up!
133
- assert @call.cancelled?
134
- @call.customer_hangs_up!
135
- assert @call.cancelled?
133
+ should "execute before callbacks" do
134
+ @call.state = 'cancelled'
135
+
136
+ @call.expects(:notify).with(:before_always).times(3)
137
+ @call.expects(:notify).with(:before_always_uniq).times(1)
138
+
139
+ @call.expects(:notify).with(:after_always).times(4)
140
+ @call.expects(:notify).with(:after_success).times(3)
141
+ @call.expects(:notify).with(:after_failure).times(1)
142
+
143
+ @call.expects(:notify).with(:after_always_uniq).times(1)
144
+ @call.expects(:notify).with { |notification, transition| notification == :after_success_uniq && transition.kind_of?(StateMachine::Transition) }.times(1)
145
+ @call.expects(:notify).with(:after_failure_uniq).times(0)
146
+
147
+ @call.customer_end!
148
+ @call.customer_end!
149
+ @call.customer_end!
150
+ assert(!@call.customer_hangs_up)
136
151
  end
137
152
 
138
153
  should "asynchronously perform event" do
@@ -166,10 +181,10 @@ class CallCenterTest < Test::Unit::TestCase
166
181
  should_flow :on => :incoming_call, :initial => :voicemail, :when => Proc.new {
167
182
  @call.stubs(:agents_available?).returns(false)
168
183
  @call.stubs(:notify)
169
- @call.stubs(:flow_url).returns('the_flow')
184
+ @call.stubs(:customer).returns('the_flow')
170
185
  } do
171
186
  should_also { assert_received(@call, :notify) { |e| e.with(:rendering_voicemail) } }
172
- and_also { assert_received(@call, :flow_url) { |e| e.with(:voicemail_complete) } }
187
+ and_also { assert_received(@call, :customer) { |e| e.with(:voicemail_complete) } }
173
188
  and_render { "Response>Say" }
174
189
  and_render { "Response>Record[action=the_flow]" }
175
190
  end
@@ -3,44 +3,63 @@ class Call
3
3
  include CommonCallMethods
4
4
 
5
5
  call_flow :state, :initial => :initial do
6
+ actor :customer do |call, event|
7
+ "/voice/calls/flow?event=#{event}&actor=customer"
8
+ end
9
+ actor :agent do |call, event|
10
+ "/voice/calls/flow?event=#{event}&actor=agent"
11
+ end
12
+
6
13
  state :initial do
14
+ response do |x, call| # To allow defining render blocks within a state
15
+ call.notify(:rendering_initial)
16
+ x.Say "Hello World"
17
+ end
18
+
7
19
  event :incoming_call, :to => :voicemail, :unless => :agents_available?
8
20
  event :incoming_call, :to => :routing, :if => :agents_available?
9
21
  event :something_crazy_happens, :to => :uh_oh
10
22
  end
11
23
 
12
24
  state :voicemail do
13
- event :customer_hangs_up, :to => :voicemail_completed
25
+ response do |x| # To allow defining render blocks outside a state
26
+ notify(:rendering_voicemail)
27
+ x.Say "Hello World"
28
+ x.Record :action => customer(:voicemail_complete)
29
+ end
30
+
31
+ customer :hangs_up, :to => :voicemail_completed
14
32
  end
15
33
 
16
34
  state :routing do
17
- event :customer_hangs_up, :to => :cancelled
35
+ customer :hangs_up, :to => :cancelled
18
36
  event :start_conference, :to => :in_conference
19
37
  end
20
38
 
21
39
  state :cancelled do
22
- event :customer_hangs_up, :to => same
23
- end
24
-
25
- # =================
26
- # = Render Blocks =
27
- # =================
40
+ after(:success, :uniq => true) { notify(:cancelled) }
28
41
 
29
- state :initial do
30
- on_render do |call, x| # To allow defining render blocks within a state
31
- call.notify(:rendering_initial)
32
- x.Say "Hello World"
33
- end
42
+ customer :hangs_up, :to => same
43
+ customer :end, :to => :ended
34
44
  end
35
45
 
36
- on_render(:voicemail) do |call, x| # To allow defining render blocks outside a state
37
- notify(:rendering_voicemail)
38
- x.Say "Hello World"
39
- x.Record :action => flow_url(:voicemail_complete)
46
+ response(:cancelled) do |x, call|
47
+ # Just for sake of comparison
40
48
  end
41
49
 
42
- on_flow_to(:cancelled) do |call, transition|
43
- notify(:cancelled)
50
+ state :ended do
51
+ after(:always) { notify(:after_always) }
52
+ after(:success) { notify(:after_success) }
53
+ after(:failure) { notify(:after_failure) }
54
+
55
+ after(:always, :uniq => true) { notify(:after_always_uniq) }
56
+ after(:success, :uniq => true) { |transition| notify(:after_success_uniq, transition) }
57
+ after(:failure, :uniq => true) { notify(:after_failure_uniq) }
58
+
59
+ before(:always) { notify(:before_always) }
60
+ before(:always, :uniq => true) { notify(:before_always_uniq) }
61
+
62
+ customer :end, :to => same
44
63
  end
45
64
  end
46
65
  end
@@ -4,7 +4,7 @@ class MultipleFlowCall
4
4
  call_flow :status, :initial => :ready do
5
5
  state :ready do
6
6
  event :go, :to => :done
7
- on_render do |call, x|
7
+ response do |x|
8
8
  x.Say "Hello World"
9
9
  end
10
10
  end
@@ -13,7 +13,7 @@ class MultipleFlowCall
13
13
  call_flow :outgoing_status, :initial => :outgoing_ready do
14
14
  state :outgoing_ready do
15
15
  event :outgoing_go, :to => :outgoing_done
16
- on_render do |call, x|
16
+ response do |x|
17
17
  x.Say "Hello Outgoing World"
18
18
  end
19
19
  end
@@ -4,7 +4,7 @@ class NonStandardCall
4
4
  call_flow :status, :initial => :ready do
5
5
  state :ready do
6
6
  event :go, :to => :done
7
- on_render do |call, x|
7
+ response do |x|
8
8
  x.Say "Hello World"
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: call_center
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 3
10
- version: 0.0.3
9
+ - 4
10
+ version: 0.0.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Henry Hsu
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-09-29 00:00:00 -07:00
18
+ date: 2011-10-23 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -231,6 +231,7 @@ files:
231
231
  - init.rb
232
232
  - lib/call_center.rb
233
233
  - lib/call_center/core_ext/object_instance_exec.rb
234
+ - lib/call_center/flow_callback.rb
234
235
  - lib/call_center/state_machine_ext.rb
235
236
  - lib/call_center/test/dsl.rb
236
237
  - test/call_center_test.rb