Rapid and painless creation of complex IVRs has always been one of the defining features of Adhearsion for beginning and advanced programmers alike. Through the #menu
DSL method, the framework abstracts and packages the output and input management and the complex state machine needed to implement a complete menu with audio prompts, digit checking, retries and failure handling, making creating menus a breeze.
The menu DSL has received a major overhaul in Adhearsion 2.0, with the goals of clarifying syntax and adding functionality.
Call Controllers
Adhearsion 2.0 is now using a new routing architecture with powerful and flexible call controllers. Call controllers allow your code to be grouped into classes, making testing easier and improving the readability of your code. For more information see Ben Langfeld's excellent introduction to call controllers and routing.
The Old Way
The Adhearsion 1.x menu structure used to work as follows, with the now obsolete concept of a dialplan and context.
foo { menu "Press 1 for Administration", "Press 2 for Tech Support", :timeout => 8.seconds, :tries => 3 do |link| link.admin 1 link.tech 2 link.on_invalid do speak 'Invalid input' end link.on_premature_timeout do speak 'Sorry' end link.on_failure do speak 'Goodbye' hangup end end } admin { speak 'You have reached the Administration department' } tech { speak 'You have reached the Technical Support office' }
The main shortcomings of this structure were mostly related to the inherent difficulty of maintaining a complex dialplan all in a single file, or breaking it up in helper components with no imposed logical structure. The old DSL was also not very intuitive, as you were specifying the target for the link before the pattern, in addition to having the block argument as a basically redundant part.
The New & Improved Way
The focus for the menu DSL in Adhearsion 2.0 was primarily on improving its functionality to work with call controllers and to fit the new framework structure. Working towards those goals, the menu definition block was streamlined while keeping readability and the general functionality of 1.x.
class MyController < Adhearsion::CallController def run answer menu "Where can we take you today?", :timeout => 8.seconds, :tries => 3 do match 1, BooController match "2", MyOtherController match 3, 4, { pass YetAnotherController } match 5, FooController match 6..10 do |dialed| say_dialed dialed end timeout { do_this_on_timeout } invalid do invoke InvalidController end failure do speak 'Goodbye' hangup end end speak "This code gets executed unless #pass is used" end def say_dialed(dialed) speak "#{dialed} was dialed" end def do_this_on_timeout speak 'Timeout' end end
The first arguments to #menu
are a list of sounds to play, as accepted by #play
, including strings for TTS, Date and Time objects, and file paths. #play
and the other input and output methods, all renovated, will be covered in a subsequent post. Sounds will be played at the beginning of the menu and after each timeout or invalid input, if the maximum number of tries has not been reached yet.
The :tries
and :timeout
options respectively specify the number of tries before going into failure, and the timeout in seconds allowed before the first and each subsequent digit input.
The most important section is the following block, which specifies how the menu will be constructed and handled.
The #match
method takes an Integer, a String, a Range or any number of them as the required input(s) for the match payload to be executed. The last argument to a #match
is either the name of a CallController, which will be invoked, or a block to be executed. Matched input is passed in to the associated block, or to the controller through it instance variable @options[:extension].
#menu
executes the payload for the first exact unambiguous match it finds after each input or timing out. In a situation where there might be overlapping patterns, such as 10 and 100, #menu
will wait for timeout after the second digit.
Internally, the state machine has been re-implemented without using exceptions as a mean for flow control, which was a concern for #menu
usage in begin..rescue blocks.
#timeout
, #invalid
and #failure
replace #on_invalid
, #on_premature_timeout
and #on_failure
. All of them only accept blocks as payload, but #pass
or #invoke
can be used to execute controllers inside them.
#invalid
has its associated block executed when the input does not possibly match any pattern. #timeout
block is run when time expires before or between input digits, without there being at least one exact match. #failure
runs its block when the maximum number of tries is reached without an input match.
Execution of the current context resumes after #menu
finishes. If you wish to jump to an entirely different controller, #pass
can be used. #menu
will return :failed if failure was reached, or :done if a match was executed.
Conclusions
The menu DSL is the only the first of many consumer-facing innovations Adhearsion 2.0 contains.
It showcases the driving force behind the new version: to have Adhearsion move out of its box as a companion to Asterisk to become a general purpose telephony framework.
More posts and examples will be published showing what you can build with Adhearsion 2.0. Stay tuned!