Ben Langfeld edited this page Apr 11, 2012 · 4 revisions

DEPRECATION NOTICE: This is old documentation relevant to Adhearsion 1.x and will soon be removed. See the main documentation for up-to-date info.

The menu() method solves the problem of building enormous input-fetching state machines in Ruby without first-class message passing facilities or an external DSL. In software development parlance, you can call the menu() method a rules engine because a user of the feature simply specifies rules for jumping between contexts; rules can be specified in any order.

Here is an example dialplan which uses the menu() command effectively:

from_pstn {
  menu 'welcome', 'for-spanish-press-8', 'main-ivr', :timeout => 8.seconds, :tries => 3 do |link|
    link.shipment_status  1
    link.ordering         2
    link.representative   4
    link.spanish          8
    link.employee         900..999
    link.on_invalid { play 'invalid' }
    link.on_premature_timeout do |str|
      play 'sorry'
    end
    link.on_failure do
      play 'goodbye'
      hangup
    end
  end
}

shipment_status {
  # Fetch a tracking number and pass it to a web service.
}

ordering {
  # Enter another menu that lets them enter credit card
  # information and place their order over the phone.
}

representative {
  # Place the caller into a queue
}

spanish {
  # Special options for the spanish menu.
}

employee {
  dial "SIP/#{extension}"
}

The main detail to note is the declarations within the menu() command's block. Each line seems to refer to a link object executing a seemingly arbitrary method with an argument that's either a number or a Range of numbers. The +link+ object collects these arbitrary method invocations and assembles a set of rules. The seemingly arbitrary method name is the name of the context the menu should jump to in case its argument (the pattern) is found to be a match.

With these context names and patterns defined, the +menu()+ command plays, in sequence, the sound files you supply as arguments, stopping playback abruptly if the user enters a digit. If no digits were pressed when the files finish playing, it waits +:timeout+ seconds. If no digits are pressed after the timeout, it executes the +on_premature_timeout+ hook you define (if any) and then tries again a maximum of +:tries+ times. If digits are pressed that result in no possible match, it executes the +on_invalid+ hook. When/if all tries are exhausted with no positive match, it executes the +on_failure+ hook after the other hook (e.g. +on_invalid+, then +on_failure+).

##Database integration##

To do database integration, it's recommended to programatically execute methods on the link object within the block. For example:

menu do |link|
  for employee in Employee.find(:all)
    link.internal employee.extension
  end
end

# or this more efficient and Rubyish way
menu do |link|
  link.internal *Employee.find(:all).map(&:extension)
end

If this second example seems too Ruby-centric, here's further explanation:

Employee.find(:all)

effectively does a "SELECT * FROM employees" on the database with ActiveRecord, returning (what you'd think is) an Array. The...

map(&:extension)

...means "replace every instance in this Array with the result of calling extension on that object". Now you have an Array of every extension in the database. The splat operator * before the argument changes the argument from being one argument (an Array) into a sequence of n arguments, where n is the number of items in the Array it's "splatting". Lastly, these arguments are passed to the internal method, the name of a context which will handle dialing this user if one of the supplied patterns matches.

##Handling a successful pattern match##

Let's say that the user's input successfully matched one of the patterns returned by Employee.find. When it jumps to the internal context, that context can access the variable entered through the extension variable. This makes the menu() command feel much more first-class in the Adhearsion dialplan grammar and decouples the receiving context from the menu that caused the jump. After all, the context doesn't necessarily need to be the endpoint from a menu; it can be its own entry point, making menu() effectively a pipeline of re-creating the call.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.