Simple Ruby on Rails app using Twilio API

Twilio created cloud telephony services made mainly for developers. I’ve been meaning to figure out how to use their service, and I had a chance to develop a Ruby on Rails app using Twilio app called Phonein. You can check out the source code here. It’s nothing fancy, and totally not optimized. I also used Twitter’s Bootstrap CSS/JS package.

While Twilio had a few samples for Ruby on Rails, it was kind of outdated and a few things were incorrect. I wanted to share what I did to use Twilio API to help you come up to speed quickly.

Basically my app is for home-care professionals, and it allows them to check in and out using client’s phone. For Twilio, the followings are its requirements.

  • Identify client by phone number
  • Identify home-care professional by 6-digit identification code
  • Check in the home-care professional at the client’s location
  • Read out tasks for the home-care professional
  • Check out the home-care professional from the client’s location

Twilio has its own markup language called TwiML, and in Twilio controller I created, TwiML is used to create views (or voices). I also created certain POST actions under Twilio controller that will receive input from phone (digits punched in by a person) and use it to look up information.

1. Answer incoming call

When you sign up with Twilio, you can configure destination URL Twilio will invoke when it receives an incoming call. For mine, it would be “http://phonein.herokuapp.com/twilios/incoming”. So, I have “incoming” action defined in “Twilios” controller.

Controller in app/controller/twilios_controller.rb

Just like a regular Ruby on Rails app, controller gets objects ready for the view. It looks up client using the incoming phone number. If client is not found, it says (instead of displaying) an error message and hangs up. If client is found, it asks user to enter a 6-digit code. @post_to object contains URL that will be invoked after user interacts with Twilio. In our case, it’s after user enters code.

  
class TwiliosController < ApplicationController      
  BASE_URL = "http://phonein.herokuapp.com/twilios"         

  def incoming          
    # Get client by phone number          
    client_phone = params['From'][2..params['From'].size]          
    @client = Client.find_by_phone(client_phone)               
    
    if @client.nil?               
      render :action => "no_client.xml.builder"
    else
      @post_to = BASE_URL + "/verify?client_id=#{@client.id}"
      render :action => "incoming.xml.builder", :layout => false
    end
  end
end

View in app/views/twilios/incoming.xml.builder

xml.instruct!
xml.Response do
  xml.Gather(:action => @post_to, :numDigits => 6) do
    xml.Say "Welcome to #{@client.name}'s residence. Please enter your 6 digit code."
  end
end

View in app/views/twilios/no_client.xml.builder

xml.instruct!
xml.Response do
  xml.Say "Client could not be found."
  xml.Hangup
end

2. Verifies home-care professional, checks in, and reads tasks.

Next, look up the home-care professional by the 6-digit code. The numbers user punched will be in ‘Digits’ parameter. If user is found but has not checked in before, it checks in the user and gives some options. If user has already checked in before, user is presented with a few options including an option to check out.

Controller in app/controller/twilios_controller.rb

In addition to incoming action.

class TwiliosController < ApplicationController   

  def verify     
    @client = Client.find(params[:client_id])         
    @agent = Agent.find_by_code(params['Digits'])          
    if @agent.nil?       
      # If no agent is found, say "no agent found" error message and hang up.       
      @post_to = BASE_URL + "/verify?client_id=#{@client.id}"       
      render :action => "no_agent.xml.builder"
      return
    else
      if @agent.checked_in?(@client.id) 
        @message = "You have already checked in."
      else
        @agent.check_in(@client.id)
        @message = "Now you are checked in."
      end
    end 

    # Default action is direction
    @post_to = BASE_URL + "/direction?agent_id=#{@agent.id}&client_id=#{@client.id}"
    render :action => "direction.xml.builder", :layout => false
  end
end

View in app/views/twilios/direction.xml.builder

xml.instruct!
xml.Response do
  xml.Gather(:action => @post_to, :numDigits => 1) do
    xml.Say "Welcome #{@agent.name}."
    xml.Say "#{@message}"
    xml.Say "Please press 1 to read the task. Press 2 to check out. Press 3 to hear about Yang. Or Press 4 to hang up."
  end
end

View in app/views/twilios/no_agent.xml.builder

xml.instruct!
xml.Response do
  xml.Gather(:action => @post_to, :numDigits => 6) do  
    xml.Say "Agent could not be found. Please enter your 6 digit code."
  end
end

3. Read tasks, check out or hear about Yang

In addition to incoming and verify actions.

This is where main messages are configured and played. Depending on the option user chooses, it will either 1) say the tasks again, 2) check the user out, 3) say a few things about Yang, or 4) just hang up.

class TwiliosController < ApplicationController   

  def direction         
    @client = Client.find(params[:client_id])     
    @agent = Agent.find(params[:agent_id])     
    @message = @client.task_list     
    @post_to = BASE_URL + "/direction?agent_id=#{@agent.id}&client_id=#{@client.id}"          

    # 1 to hear the tasks again, 2 to check out, 3 to hang up.     
    if params['Digits'] == '1'       
      render :action => "direction.xml.builder", :layout => false
    elsif params['Digits'] == '2'
      @agent.check_out(@client.id)
      @goodbye_message = "Thank you for your service today."
      render :action => 'goodbye.xml.builder', :layout => false
    elsif params['Digits'] == '3'
      @message = "Yang is the most awesome guy ever - both personally and professionally. He is pretty sexy, too."
      render :action => 'direction.xml.builder', :layout => false
    elsif params['Digits'] == '4'
      @goodbye_message = "Have a great day."
      render :action => 'goodbye.xml.builder', :layout => false
    end
  end
end

View in app/views/twilios/goodbye.xml.builder

xml.instruct!
xml.Response do
  xml.Say "#{@goodbye_message}"
  xml.Say "Good-bye."
  xml.Hangup
end

Have fun!

That’s it! My codes are so not refactored and optimized. I did it to see what I can do with Twilio app. I hope this gives you a tip of what you can do with Twilio API. Go bananas!