Skip to content
Mauricio Gomes edited this page Nov 7, 2019 · 1 revision

Controllers are responsible for handling incoming requests and getting a response back to the user (replies).

Naming Conventions

The controller's methods, also referred to as actions, must be named after the flow's states. So for example, given the flow:

flow :onboard do
  state :say_welcome
  state :ask_for_phone
  state :get_phone, fails_to: :ask_for_phone
end

The corresponding controller would be:

class OnboardsController < BotController
  def say_welcome

  end

  def ask_for_phone

  end

  def get_phone

  end
end

bot_controller.rb

Every Stealth project comes with a default bot_controller.rb:

class BotController < Stealth::Controller

  before_action :current_user

  def route
    if current_session.present?
      step_to session: current_session
    else
      step_to flow: 'hello', state: 'say_hello'
    end
  end

end

All of your controllers will inherit from BotController:

class QuotesController < BotController

end

Route Method

You can implement any method in BotController. Typically you will implement methods like current_user and methods for handling message payloads. The one method that BotController must implement is the route method.

The route method is called for every incoming message. In its default implementation, the route method checks whether the user already has a session, and if so routes them to that controller and action. If the user does not yet have a session, it will route them to the hello flow and say_hello action.

Stepping and Updating Sessions

Stealth provides a few built-in methods to help you route a user through your bot.

step_to

The step_to method is used to update the session and immediately move the user to the specified flow and/or state. step_to can accept a flow, a state, or both. step_to is often used after a say action where the next action typically doesn't require user input.

Some examples of the different parameters:

step_to flow: 'hello' - Sets the session's flow to hello and the state will be set to the first state in that flow (as defined by the flow_map.rb file). The corresponding controller action in the HellosController would also be called.

step_to state: 'say_hello' - Sets the session's state to say_hello and keeps the flow the same. The say_hello controller action would also be called.

step_to flow: 'hello', state: 'say_hello' - Sets the session's flow to hello and the state to say_hello. The say_hello controller action of the HellosController controller would also be called.

update_session_to

Similar to step_to, update_session_to is used to update the user's session to a flow and/or state. It accepts the same parameters. However, update_session_to does not immediately call the respective controller action. update_session_to is typically used after an ask action where the next action is waiting for user input. So by asking a user for input, then updating the session, it ensures the response the user sends back can be handled by the get action.

Some examples of the different parameters:

update_session_to flow: 'quote' - Sets the session's flow to quote and the state will be set to the first state in that flow (as defined by the flow_map.rb file).

update_session_to state: 'ask_zip_code' - Sets the session's state to ask_zip_code and keeps the flow the same.

step_to flow: 'quote', state: 'ask_zip_code' - Sets the session's flow to quote and the state to ask_zip_code.

send_replies

send_replies will instruct the Reply to construct the reply and transmit them. Not all of your controller actions will send replies. Typically in get action, you'll get a user's response, perform some action, and then send a user to a new state without replying.

The send_replies method does not take any parameters:

class ContactsController < BotController
  def say_contact_us
    send_replies
  end
end

This would render the reply contained in replies/contacts/say_contact_us.yml. See Reply Variants for additional naming options.

In addition to calling send_replies without arguments, starting with Stealth 2.2.0, send_replies supports to additional arguments: inline and custom_reply.

inline

Inline replies allow you to specify the reply to send inline, within the controller. This can be useful if, for example, you store your replies in a database rather than the filesystem. The replies should mimic the structure of the replies YAML file, which is to say it should be an Array of replies like this:

reply = [
  { 'reply_type' => 'text', 'text' => 'Hi, Morty. Welcome to Stealth bot...' },
  { 'reply_type' => 'delay', 'duration' => 2 },
  { 'reply_type' => 'text', 'text' => 'We offer users an awesome Ruby framework for building chat bots.' }
]

Inline replies do not support preprocessing (ERB). You'll have to preprocess any strings before sending the inline reply. To send the inline reply from above:

send_replies inline: reply

custom_reply

If you'd like to specify a different reply file to send rather than the one that Stealth will select by default, you can do so with the custom_reply argument:

def say_custom_reply
  send_replies custom_reply: 'messages/say_offer'
end

This will tell Stealth to look in the bot/replies/messages directory for the reply. Inside of that directory, it will look for say_offer.yml, say_offer.yml.erb and also any service specific variants. Stealth will select the right reply using the same heuristic as it does for normal replies.

step_to_in

The step_to_in method is similar to step_to. The only difference is that instead of calling the respective controller action immediately, it calls it after a specified duration. It can also take a flow, state, or both.

For example:

step_to_in 8.hours, flow: 'hello', state: 'say_hello'

This will set the user's session to the hello flow and say_hello state in 8 hours after being called. It will then immediately call that responsible controller action.

step_to_at

The step_to_at method is similar to step_to. The only difference is that instead of calling the respective controller action immediately, it calls it at a specific date and time. It can also take a flow, state, or both.

For example:

step_to_at DateTime.strptime("01/23/23 20:23", "%m/%d/%y %H:%M"), flow: 'hello', state: 'say_hello'

This will set the user's session to the hello flow and say_hello state on Jan 23, 2023 @ 20:23. It will then immediately call that responsible controller action.

set_back_to

set_back_to serves a similar purpose as previous_session, however, instead of being automatically set by Stealth set_back_to is completely user controlled. It's useful when paired with step_back.

It takes the same parameters as step_to:

set_back_to state: :say_hello
set_back_to flow: :hello, state: :say_hello
set_back_to flow: session: previous_session

All three commands above are valid and will store the back_to session as it is called. This will allow you to reference it later like in cases where a user has been sent to a multi-state flow and needs to "return back" to the state specified by set_back_to.

step_back

Used in conjunction with set_back_to, step_back will step the user to the flow and state specified by set_back_to. If a back_to session has not been set before step_back is called, Stealth will raise a Stealth::Errors::InvalidStateTransition exception.

do_nothing

This method is available as a convenience for when you do not wish to send a reply nor send the user to another state. It's useful for states that trap the user (like a reminder or the last level of your catch_all flow).

For example:

def level3
  do_nothing
end

Available Data

Within each controller action, you have access to a few objects containing information about the session and the message the being processed.

current_session

The user's session is available to you at anytime using current_session. This is a Stealth::Session object. It has a few notable methods:

flow_string: Returns the name of the flow. state_string: Returns the name of the state. current_session + 2.states: Returns a new session object 2 states after the current state. If we've passed the last state, the last state is returned. current_session - 2.states: Returns a new session object 2 states before the current state. If we've passed the first state, the first state is returned.

current_message

The current message being processed is available to you at anytime using current_message. This is a Stealth::ServiceMessage object. It has a few notable methods:

sender_id: The ID of the user sending the message. This will vary based on the service integration.

target_id: The ID of the target. This will vary based on the service sending the message, but for Facebook it will be the page_id of the Facebook page receiving the message and for SMS will be the number receiving the SMS message. For other services, this may be nil. timestamp: Ruby DateTime object of when the message was transmitted. service: String indicating the service from where the message originated (i.e., 'facebook'). message: String of the message contents. payload: This will vary by service integration. location: This will vary by service integration. attachments: This will vary by service integration. referral: This will vary by service integration.

current_service

This will be a string indicating the service from where the message originated (i.e., 'facebook' or 'twilio')

has_location?

Returns true or false depending on whether or not the current_message contains location data.

has_attachments?

Returns true or false depending on whether or not the current_message contains attachments.

current_session_id (previously current_user_id)

A convenience method for accessing the session's ID.