Employee Directory with Ruby and Rails

Learn how to implement an employee directory that you can query using SMS. Request information from anyone at your company just by sending a text message to a Twilio Number

Here is how it works at a high level:

  • The user sends a SMS with an Employee's name to the Twilio number.
  • The user receives information for the requested Employee.

Handle Twilio's SMS Request

When your Twilio Number receives an SMS, Twilio will make a POST request to /directory/search asking for TwiML instructions.

Once the application identifies one of the 3 possible scenarios (single partial match, multiple partial match or no match), it will send a TwiML response to Twilio. This response will instruct Twilio to send an SMS Message back to the user.

Loading Code Samples...
Language
class DirectoryController < ApplicationController
  skip_before_action :verify_authenticity_token

  def search
    suggestion = Suggestion.new(cookies)
    query      = QueryBuilder.new(suggestion, params['Body']).build
    message, image_url = EmployeeFinder.new(suggestion).apply(query)

    render xml: TwimlResponseCreator.create(message, image_url)
  end
end
app/controllers/directory_controller.rb
Employee Directory Controller

app/controllers/directory_controller.rb

Let's take a closer look at each one of the scenarios.

Find an Exact Employee Match

This is the simplest scenario. We query our database expecting to find an employee whose first and last name are exactly like the ones we specified in the SMS that was sent to our Twilio number. If a match is found, a message containing this employee's information is built and sent to Twilio as TwiML instructions.

Loading Code Samples...
Language
module Matchers
  class PerfectMatch
    def self.match(query, suggestion)
      employee = Employee.perfect_match(query).first
      if employee
        suggestion.destroy
        [CreateMessage.with_employee_info(employee), employee.image_url]
      end
    end
  end
end
lib/matchers/perfect_match.rb
Finding an Exact Employee Match

lib/matchers/perfect_match.rb

If no match is found we'll try to do a single partial match. That is our next possible scenario.

Find a Single Partial Employee Match

If no employee's exact match is found on the database we'll try to find a close match using Fuzzily Gem. In this scenario we'll verify that only 1 partial match is obtained. When a single partial match is found, the user will receive a suggestion containing it. The user will be given the opportunity to reply with the word yes to this SMS. If that were the case, the user will receive the information for the employee that was chosen.

We'll show you how the suggestions are stored for the SMS conversation using Twilio Cookies a few steps later.

Loading Code Samples...
Language
module Matchers
  class SinglePartialMatch
    def self.match(query, suggestion)
      employees = Employee.partial_match(query)
      if employees.count == 1
        employee = employees.first
        suggestion.store(employee.name)
        [CreateMessage.with_suggestion(employee), nil]
      end
    end
  end
end
lib/matchers/single_partial_match.rb
Find a Single Partial Employee Match

lib/matchers/single_partial_match.rb

Now that we know how to find individual employees, lets see how we can get multiple results.

Find Multiple Partial Employee Matches

At this point we have already tried to use the user's query as an exact employee match and a single partial match. Now we'll try to get a partial match that returns more than one result. Just like in the previous scenario, we'll use Twilio Cookies to store suggestions. Then only difference here is that we need to store a Hash containing index suggestions so that the user can reply with a number that references one of the suggestions in order to get all the employee's information.

The last scenario is simple. If none of the previous scenarios occur, it means that there is no employee in the database that matches the user's query. In that case, a reply will be sent to the user explaining that their query doesn't match any of the employees found on the database.

Loading Code Samples...
Language
module Matchers
  class MultiplePartialMatch
    def self.match(query, suggestion)
      employees = Employee.partial_match(query)
      if employees.count > 1
        suggestions = suggested_employees(employees)
        suggestion.store_all(suggestions)
        [CreateMessage.with_suggestions(suggestions), nil]
      end
    end

    def self.suggested_employees(employees)
      employees.each_with_index.inject({}) do |suggestions, (employee, index)|
        suggestions.merge(Hash[index.succ.to_s, employee.name])
      end
    end

    private_class_method :suggested_employees
  end
end
lib/matchers/multiple_partial_match.rb
Find Multiple Partial Employee Matches

lib/matchers/multiple_partial_match.rb

Now that we have full search functionality lets see how we can optimize it by caching search suggestions in the browser's cookies.

Storing Suggestions With Cookies

When a user gets a partial match by searching the employee directory, we reply with one or more suggestions. We need to store this suggestions, so the next time the user sends an SMS we know this is not a query for a new employee, but a selection of one of the suggestions.

We'll use Twilio Cookies to store suggestions. They will allow you to keep track of an SMS conversation between multiple numbers and your Twilio powered application.

Loading Code Samples...
Language
require 'yaml'

class Suggestion < Struct.new(:cookies)
  def store(employee_name)
    cookies[:suggestion] = employee_name
  end

  def store_all(suggestions)
    cookies[:suggestions] = suggestions.to_yaml
  end

  def single
    cookies.fetch(:suggestion, '')
  end

  def multiple
    YAML.load(cookies.fetch(:suggestions, '')) || {}
  end

  def destroy
    cookies[:suggestion]  = nil
    cookies[:suggestions] = nil
  end

  def multiple?
    !!cookies[:suggestions]
  end

  def single?
    !!cookies[:suggestion]
  end
end
lib/suggestion.rb
Storing Suggestions With Cookies

lib/suggestion.rb

That's it! We have just implemented employee directory using Ruby on Rails. Now you can get your employee's information by texting a Twilio number.

Where to next?

If you're a Ruby developer working with Twilio, you might also enjoy these tutorials:

Ruby Quickstart: Make Outgoing Calls from the Browser

Learn how to use Twilio Client to make browser-to-phone and browser-to-browser calls with ease.

ETA Notifications with Ruby and Rails

Learn how to implement ETA Notifications using Ruby on Rails and Twilio.

Did this help?

Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio to let us know what you think!

Agustin Camino
Jose Oliveros
David Prothero

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
class DirectoryController < ApplicationController
  skip_before_action :verify_authenticity_token

  def search
    suggestion = Suggestion.new(cookies)
    query      = QueryBuilder.new(suggestion, params['Body']).build
    message, image_url = EmployeeFinder.new(suggestion).apply(query)

    render xml: TwimlResponseCreator.create(message, image_url)
  end
end
module Matchers
  class PerfectMatch
    def self.match(query, suggestion)
      employee = Employee.perfect_match(query).first
      if employee
        suggestion.destroy
        [CreateMessage.with_employee_info(employee), employee.image_url]
      end
    end
  end
end
module Matchers
  class SinglePartialMatch
    def self.match(query, suggestion)
      employees = Employee.partial_match(query)
      if employees.count == 1
        employee = employees.first
        suggestion.store(employee.name)
        [CreateMessage.with_suggestion(employee), nil]
      end
    end
  end
end
module Matchers
  class MultiplePartialMatch
    def self.match(query, suggestion)
      employees = Employee.partial_match(query)
      if employees.count > 1
        suggestions = suggested_employees(employees)
        suggestion.store_all(suggestions)
        [CreateMessage.with_suggestions(suggestions), nil]
      end
    end

    def self.suggested_employees(employees)
      employees.each_with_index.inject({}) do |suggestions, (employee, index)|
        suggestions.merge(Hash[index.succ.to_s, employee.name])
      end
    end

    private_class_method :suggested_employees
  end
end
require 'yaml'

class Suggestion < Struct.new(:cookies)
  def store(employee_name)
    cookies[:suggestion] = employee_name
  end

  def store_all(suggestions)
    cookies[:suggestions] = suggestions.to_yaml
  end

  def single
    cookies.fetch(:suggestion, '')
  end

  def multiple
    YAML.load(cookies.fetch(:suggestions, '')) || {}
  end

  def destroy
    cookies[:suggestion]  = nil
    cookies[:suggestions] = nil
  end

  def multiple?
    !!cookies[:suggestions]
  end

  def single?
    !!cookies[:suggestion]
  end
end