Xip Catch-Alls are designed to handle the error cases within bots. Either the bot doesn't understand what a user typed or an error occurred. If this were a webpage, we'd see the dreaded HTTP 500 error page.
Catch-Alls are designed to move beyond simple "I don't understand" messages and help users get back on track. The better your CatchAlls, the better your bot.
The catch_all
flow is automatically triggered when either of two things happens within a controller action:
The action fails to progress a user.
An Exception is raised.
If an action within CatchAllsController
raises an exception, it won't fire another Catch-All to prevent loops.
Xip keeps track of how many times a Catch-All is triggered for a given session. This allows you to build experiences in which the user is provided different responses for subsequent failures.
So for example, if in the hello
flow and say_hello
state an exception is raised, then Catch-All Level 1 will be called. If the user is return to that same flow and state and another exception is raised, Catch-All Level 2 will be called. This continues until you either run out of Catch-All states or if the Catch-All counter resets.
The Catch-All counter currently resets after 15 minutes. This is per flow and state. So a user may encounter a Catch-All elsewhere in your bot and it will utilize a separate Catch-All counter.
By default, a Xip bot comes with Catch-All Level 1 already defined. Here is the default CatchAllsController
and associated reply:
class CatchAllsController < BotController​def level1send_replies​if fail_session.present?step_to session: fail_sessionelsestep_to session: previous_session - 2.statesendend​private​def fail_sessionprevious_session.flow.current_state.fails_toend​end
- reply_type: texttext: Oops. It looks like something went wrong. Let's try that again
In the controller action, we check if the previous_session
(the one that failed) specified a fails_to
state. If so, we send the user there. Otherwise, we send the user back 2 states.
Sending a user back two states is a pretty good generic action. Going back 1 state takes us back to the action that failed. Since the actions most likely to fail are get
actions, or actions that deal with user responses, going back 2 states usually takes us back to the original "question".
Where possible, it's better to specify a fails_to
state so Xip doesn't incorrectly guess where to send your user back.
If you would like to extend the experience, add a level2
controller action and associated reply (and update the FlowMap
). You can go as far as you want. CatchAlls have no limit, just make sure you increment using the standardized method names of level1
, level2
, level3
, level4
, etc.
If a user has encountered the maximum number of CatchAll levels that have been defined, it won't attempt to call any more levels.
For the last Catch-All state, you'll probably want to prompt the user to contact support or send them to a special menu to choose from.
As mentioned in the Triggering section above, there are two reasons a Catch-All triggers. Xip will provide the CatchAllsController
with that reason so you can customize your messages and take the appropriate action.
So if for example your bot just didn't recognize the message sent by the user, you may ask the user to repeat. If however, your database is down, you might other action.
Here is an example usage:
class CatchAllsController < BotControllerbefore_action :set_catch_all_reason​def level1send_catch_all_replies('level1')​if fail_session.present?step_to session: fail_session, pos: -1elsestep_to session: previous_session - 2.states, pos: -1endend​def level2send_catch_all_replies('level2')end​def level3send_catch_all_replies('level3')end​private​def fail_sessionprevious_session.flow.current_state.fails_toend​def send_catch_all_replies(level)if @reason == :unrecognized_messagesend_replies(custom_reply: "catch_alls/#{level}_unrecognized")elsesend_replies(custom_reply: "catch_alls/#{level}")endend​def set_catch_all_reason@reason = case current_message.catch_all_reason[:err].to_swhen 'Xip::Errors::UnrecognizedMessage':unrecognized_messageelse:system_errorendend​end​
In this CatchAllsController
we have two sets of Catch-All replies. One for when the message was unrecognized and another for when we've encountered a system error. We dynamically send the appropriate reply based on the @reason
instance variable that we set with the before_action
in the controller.