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 +81 -69
 - data/Rakefile +1 -1
 - data/VERSION +1 -1
 - data/call_center.gemspec +3 -2
 - data/lib/call_center.rb +22 -30
 - data/lib/call_center/flow_callback.rb +69 -0
 - data/lib/call_center/state_machine_ext.rb +83 -11
 - data/test/call_center_test.rb +31 -16
 - data/test/examples/call.rb +38 -19
 - data/test/examples/multiple_flow_call.rb +2 -2
 - data/test/examples/non_standard_call.rb +1 -1
 - metadata +5 -4
 
    
        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 => : 
     | 
| 
       36 
     | 
    
         
            -
                     
     | 
| 
       37 
     | 
    
         
            -
                      event 
     | 
| 
       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 : 
     | 
| 
       42 
     | 
    
         
            -
                       
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
       46 
     | 
    
         
            -
                       
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
       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. 
     | 
| 
       62 
     | 
    
         
            -
                @call. 
     | 
| 
       63 
     | 
    
         
            -
                @call. 
     | 
| 
      
 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 
     | 
    
         
            -
                 
     | 
| 
       100 
     | 
    
         
            -
                   
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
                   
     | 
| 
       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  
     | 
| 
      
 115 
     | 
    
         
            +
                  <Say>This is Henry!</Say>
         
     | 
| 
       111 
116 
     | 
    
         
             
                </Response>
         
     | 
| 
       112 
117 
     | 
    
         | 
| 
       113 
118 
     | 
    
         
             
            Callbacks
         
     | 
| 
       114 
119 
     | 
    
         
             
            ---------
         
     | 
| 
       115 
120 
     | 
    
         | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
      
 121 
     | 
    
         
            +
            You have control over what you want to happen before/after state transitions:
         
     | 
| 
       117 
122 
     | 
    
         | 
| 
       118 
     | 
    
         
            -
                 
     | 
| 
       119 
     | 
    
         
            -
                   
     | 
| 
       120 
     | 
    
         
            -
                   
     | 
| 
       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 
     | 
    
         
            -
             
     | 
| 
       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 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
      
 136 
     | 
    
         
            +
            For example,
         
     | 
| 
       129 
137 
     | 
    
         | 
| 
       130 
     | 
    
         
            -
                 
     | 
| 
       131 
     | 
    
         
            -
             
     | 
| 
       132 
     | 
    
         
            -
                  state :answered do
         
     | 
| 
       133 
     | 
    
         
            -
                    ...
         
     | 
| 
       134 
     | 
    
         
            -
                  end
         
     | 
| 
      
 138 
     | 
    
         
            +
                state :voicemail do
         
     | 
| 
      
 139 
     | 
    
         
            +
                  before(:always) { log_start_event }
         
     | 
| 
       135 
140 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
                   
     | 
| 
       137 
     | 
    
         
            -
             
     | 
| 
       138 
     | 
    
         
            -
                  end
         
     | 
| 
      
 141 
     | 
    
         
            +
                  after(:always) { log_end_event }
         
     | 
| 
      
 142 
     | 
    
         
            +
                  after(:failure) { notify_airbrake }
         
     | 
| 
       139 
143 
     | 
    
         | 
| 
       140 
     | 
    
         
            -
                   
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
                  end
         
     | 
| 
      
 144 
     | 
    
         
            +
                  after(:success, :uniq => true) { notify_browser }
         
     | 
| 
      
 145 
     | 
    
         
            +
                  after(:failure, :uniq => true) { notify_cleanup_browser }
         
     | 
| 
       143 
146 
     | 
    
         
             
                end
         
     | 
| 
       144 
     | 
    
         
            -
                ...
         
     | 
| 
       145 
147 
     | 
    
         | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
      
 148 
     | 
    
         
            +
            Motivation
         
     | 
| 
      
 149 
     | 
    
         
            +
            ----------
         
     | 
| 
       147 
150 
     | 
    
         | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
       149 
     | 
    
         
            -
             
     | 
| 
       150 
     | 
    
         
            -
             
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
             
     | 
| 
       154 
     | 
    
         
            -
             
     | 
| 
       155 
     | 
    
         
            -
             
     | 
| 
      
 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. 
     | 
| 
      
 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. 
     | 
| 
      
 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- 
     | 
| 
      
 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( 
     | 
| 
       54 
     | 
    
         
            -
                   
     | 
| 
       55 
     | 
    
         
            -
                   
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
                     
     | 
| 
      
 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  
     | 
| 
       76 
     | 
    
         
            -
                   
     | 
| 
       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  
     | 
| 
      
 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 : 
     | 
| 
       4 
     | 
    
         
            -
              attr_accessor : 
     | 
| 
      
 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  
     | 
| 
       7 
     | 
    
         
            -
                @ 
     | 
| 
       8 
     | 
    
         
            -
                @ 
     | 
| 
      
 8 
     | 
    
         
            +
              def response(state_name, &blk)
         
     | 
| 
      
 9 
     | 
    
         
            +
                @response_blocks ||= {}
         
     | 
| 
      
 10 
     | 
    
         
            +
                @response_blocks[state_name] = blk
         
     | 
| 
       9 
11 
     | 
    
         
             
              end
         
     | 
| 
       10 
12 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
              def  
     | 
| 
       12 
     | 
    
         
            -
                @ 
     | 
| 
       13 
     | 
    
         
            -
                @ 
     | 
| 
      
 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  
     | 
| 
      
 71 
     | 
    
         
            +
              def response(state_name = nil, &blk)
         
     | 
| 
       20 
72 
     | 
    
         
             
                if @from_state
         
     | 
| 
       21 
     | 
    
         
            -
                  @queued_sends << [[: 
     | 
| 
      
 73 
     | 
    
         
            +
                  @queued_sends << [[:response, @from_state], blk]
         
     | 
| 
       22 
74 
     | 
    
         
             
                else
         
     | 
| 
       23 
     | 
    
         
            -
                  @queued_sends << [[: 
     | 
| 
      
 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
         
     | 
    
        data/test/call_center_test.rb
    CHANGED
    
    | 
         @@ -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 
     | 
| 
      
 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 
     | 
| 
      
 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= 
     | 
| 
      
 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 " 
     | 
| 
       128 
     | 
    
         
            -
                  @call.state = ' 
     | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
       130 
     | 
    
         
            -
                  @call. 
     | 
| 
       131 
     | 
    
         
            -
                   
     | 
| 
       132 
     | 
    
         
            -
             
     | 
| 
       133 
     | 
    
         
            -
                   
     | 
| 
       134 
     | 
    
         
            -
                  @call. 
     | 
| 
       135 
     | 
    
         
            -
                   
     | 
| 
      
 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(: 
     | 
| 
      
 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, : 
     | 
| 
      
 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
         
     | 
    
        data/test/examples/call.rb
    CHANGED
    
    | 
         @@ -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 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
       23 
     | 
    
         
            -
                end
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                # =================
         
     | 
| 
       26 
     | 
    
         
            -
                # = Render Blocks =
         
     | 
| 
       27 
     | 
    
         
            -
                # =================
         
     | 
| 
      
 40 
     | 
    
         
            +
                  after(:success, :uniq => true) { notify(:cancelled) }
         
     | 
| 
       28 
41 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                   
     | 
| 
       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 
     | 
    
         
            -
                 
     | 
| 
       37 
     | 
    
         
            -
                   
     | 
| 
       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 
     | 
    
         
            -
                 
     | 
| 
       43 
     | 
    
         
            -
                  notify(: 
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 16 
     | 
    
         
            +
                  response do |x|
         
     | 
| 
       17 
17 
     | 
    
         
             
                    x.Say "Hello Outgoing World"
         
     | 
| 
       18 
18 
     | 
    
         
             
                  end
         
     | 
| 
       19 
19 
     | 
    
         
             
                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:  
     | 
| 
      
 4 
     | 
    
         
            +
              hash: 23
         
     | 
| 
       5 
5 
     | 
    
         
             
              prerelease: 
         
     | 
| 
       6 
6 
     | 
    
         
             
              segments: 
         
     | 
| 
       7 
7 
     | 
    
         
             
              - 0
         
     | 
| 
       8 
8 
     | 
    
         
             
              - 0
         
     | 
| 
       9 
     | 
    
         
            -
              -  
     | 
| 
       10 
     | 
    
         
            -
              version: 0.0. 
     | 
| 
      
 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- 
     | 
| 
      
 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
         
     |