# Workflow Automation with Ruby and Rails

One of the more abstract concepts you'll handle when building your business is what the *workflow* will look like.

At its core, setting up a standardized workflow is about enabling your service providers (agents, hosts, customer service reps, administrators, and the rest of the gang) to better serve your customers.

To illustrate a very real-world example, today we'll build a Ruby on Rails webapp for finding and booking vacation properties — tentatively called ***Airtng***.

Here's how it'll work:

1. A *host* creates a vacation property listing
2. A *guest* requests a reservation for a property
3. The *host* receives an SMS notifying them of the reservation request. The host can either **Accept** or **Reject** the reservation
4. The *guest* is notified whether a request was rejected or accepted

*[Learn how Airbnb used Twilio SMS to streamline the rental experience for 60M+ travelers around the world.](https://customers.twilio.com/en-us/airbnb)*

## Workflow Building Blocks

We'll be using the Twilio REST API to send our users messages at important junctures. Here's a bit more on our API:

* Sending Messages with Twilio API

```rb title="Application routes" description="config/routes.rb"
Rails.application.routes.draw do

  get "login/", to: "sessions#login", as: 'login'
  get "logout/", to: "sessions#logout"
  post "login_attempt/", to: "sessions#login_attempt"

  resources :users, only: [:new, :create, :show]

  resources :vacation_properties, path: "/properties"
  resources :reservations, only: [:new, :create]
  post "reservations/incoming", to: 'reservations#accept_or_reject', as: 'incoming'

  # Home page
  root 'main#index', as: 'home'

end
```

## The Vacation Property Model

The `VacationProperty` model belongs to the `User` who created it (we'll call this user the *host* moving forward) and contains only two properties, a `description` and an `image_url`.

It has two [associations](https://guides.rubyonrails.org/association_basics.html) in that it has many *reservations* and therefore many *users* through those reservations.

The best way to generate the model and all of the basic [CRUD scaffolding](https://curriculum.railsbridge.org/intro-to-rails/CRUD_with_scaffolding) we'll need is to use the [Rails command line](https://guides.rubyonrails.org/command_line.html) tool:

```bash
bin/rails generate scaffold VacationProperty

```

One of the benefits of using the Rails generator is that it creates all of our routes, controllers and views so that we have a fully functional CRUD interface out of the box. Nifty!

```rb title="Vacation Property Model" description="app/models/vacation_property.rb"
class VacationProperty < ActiveRecord::Base
  belongs_to :user # host
  has_many :reservations
  has_many :users, through: :reservations #guests
end
```

Let's jump into the stew and look next at the Reservation mode.

## The Reservation Model

The `Reservation` model is at the center of the workflow for this application. It is responsible for keeping track of:

* the `VacationProperty` it is associated with
* the `User` who owns that vacation property (the *host*)
* the guest name and phone number

```rb title="The Reservation Model" description="app/models/reservation.rb"
class Reservation < ActiveRecord::Base
  validates :name, presence: true
  validates :phone_number, presence: true

  enum status: [ :pending, :confirmed, :rejected ]

  belongs_to :vacation_property
  belongs_to :user

  def notify_host(force = true)
    @host = User.find(self.vacation_property[:user_id])

    # Don't send the message if we have more than one or we aren't being forced
    if @host.pending_reservations.length > 1 or !force
      return
    else
      message = "You have a new reservation request from #{self.name} for #{self.vacation_property.description}:

      '#{self.message}'

      Reply [accept] or [reject]."

      @host.send_message_via_sms(message)
    end
  end

  def confirm!
    self.status = "confirmed"
    self.save!
  end

  def reject!
    self.status = "rejected"
    self.save!
  end

  def notify_guest
    @guest = User.find_by(phone_number: self.phone_number)

    if self.status_changed? && (self.status == "confirmed" || self.status == "rejected")
      message = "Your recent request to stay at #{self.vacation_property.description} was #{self.status}."
      @guest.send_message_via_sms(message)
    end
  end
end
```

Since the reservation can only have one guest for our example we simplified the model by assigning a `name` and `phone_number` directly.

We'll cover how we did this later. Next, however, we'll zoom in on the reservation status.

## Reservation Status

First we validate some key properties and define the associations so that we can later lookup those relationships through the model. (If you'd like more context, the [Rails guide](https://guides.rubyonrails.org/active_record_basics.html) explains models and associations quite well.)

The main property we need to enable a reservation workflow is some sort of `status` that we can monitor. This is a perfect candidate for an enumerated `status` attribute.

### Enumerated Attributes

[Enumerated attributes](https://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html) allow us to store an integer in the table, while giving each status a searchable name. Here is an example:

```ruby
# reservation.pending! status: 0
reservation.status = "confirmed"
reservation.confirmed? # => true

```

```rb title="Validators and Foreign Key for the Reservation model" description="app/models/reservation.rb"
# !mark(1:9)
class Reservation < ActiveRecord::Base
  validates :name, presence: true
  validates :phone_number, presence: true

  enum status: [ :pending, :confirmed, :rejected ]

  belongs_to :vacation_property
  belongs_to :user

  def notify_host(force = true)
    @host = User.find(self.vacation_property[:user_id])

    # Don't send the message if we have more than one or we aren't being forced
    if @host.pending_reservations.length > 1 or !force
      return
    else
      message = "You have a new reservation request from #{self.name} for #{self.vacation_property.description}:

      '#{self.message}'

      Reply [accept] or [reject]."

      @host.send_message_via_sms(message)
    end
  end

  def confirm!
    self.status = "confirmed"
    self.save!
  end

  def reject!
    self.status = "rejected"
    self.save!
  end

  def notify_guest
    @guest = User.find_by(phone_number: self.phone_number)

    if self.status_changed? && (self.status == "confirmed" || self.status == "rejected")
      message = "Your recent request to stay at #{self.vacation_property.description} was #{self.status}."
      @guest.send_message_via_sms(message)
    end
  end
end
```

Once we have an attribute that can trigger our workflow events, it's time to write some callbacks. Let's look there next.

## The Reservations Controller

We'll be posting our reservation details to the `create` route from the vacation property page.

After we create the reservation we want to notify the host that she has a request pending. After she accepts or rejects it we want to notify the guest of the news.

While the `Reservation` model handles the notification, we want to keep all these actions in the controller to show our intentions.

```rb title="The Reservation Controller" description="Create a new reservation"
# !mark(8:19)
class ReservationsController < ApplicationController

  # GET /vacation_properties/new
  def new
    @reservation = Reservation.new
  end

  def create
    @vacation_property = VacationProperty.find(params[:reservation][:property_id])
    @reservation = @vacation_property.reservations.create(reservation_params)

    if @reservation.save
      flash[:notice] = "Sending your reservation request now."
      @reservation.notify_host
      redirect_to @vacation_property
    else
      flast[:danger] = @reservation.errors
    end
  end

  # webhook for twilio incoming message from host
  def accept_or_reject
    incoming = Sanitize.clean(params[:From]).gsub(/^\+\d/, '')
    sms_input = params[:Body].downcase
    begin
      @host = User.find_by(phone_number: incoming)
      @reservation = @host.pending_reservation

      if sms_input == "accept" || sms_input == "yes"
        @reservation.confirm!
      else
        @reservation.reject!
      end

      @host.check_for_reservations_pending

      sms_reponse = "You have successfully #{@reservation.status} the reservation."
      respond(sms_reponse)
    rescue
      sms_reponse = "Sorry, it looks like you don't have any reservations to respond to."
      respond(sms_reponse)
    end
  end

  private
    # Send an SMS back to the Subscriber
    def respond(message)
      response = Twilio::TwiML::Response.new do |r|
        r.Message message
      end
      render text: response.text
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def reservation_params
      params.require(:reservation).permit(:name, :phone_number, :message)
    end

end
```

Next up, let's take a look at how exactly we'll notify the lucky host.

## Notify the Host

To notify the host, we can look them up and send them an SMS message. However, how do we ensure our hosts are: *a)* responding to the correct reservation inquiry and *b)* not getting spammed?

### Pending Reservations

Our solution to both problems:

* We only notify the host of the oldest pending reservation.
* We don't send another SMS until the host has dealt with the last reservation.

To do this, we'll create a helper method on the `User` model to surface the `pending_reservations` method on a user. We'll go over that in the next step.

### Sending the SMS

If in fact the host only has one pending reservation, we're going to fire an SMS off to the host immediately.

```rb title="Send an SMS to notify the host of a new reservation" description="app/models/reservation.rb"
# !mark(10:25)
class Reservation < ActiveRecord::Base
  validates :name, presence: true
  validates :phone_number, presence: true

  enum status: [ :pending, :confirmed, :rejected ]

  belongs_to :vacation_property
  belongs_to :user

  def notify_host(force = true)
    @host = User.find(self.vacation_property[:user_id])

    # Don't send the message if we have more than one or we aren't being forced
    if @host.pending_reservations.length > 1 or !force
      return
    else
      message = "You have a new reservation request from #{self.name} for #{self.vacation_property.description}:

      '#{self.message}'

      Reply [accept] or [reject]."

      @host.send_message_via_sms(message)
    end
  end

  def confirm!
    self.status = "confirmed"
    self.save!
  end

  def reject!
    self.status = "rejected"
    self.save!
  end

  def notify_guest
    @guest = User.find_by(phone_number: self.phone_number)

    if self.status_changed? && (self.status == "confirmed" || self.status == "rejected")
      message = "Your recent request to stay at #{self.vacation_property.description} was #{self.status}."
      @guest.send_message_via_sms(message)
    end
  end
end
```

Let's now take a look at the `User` model.

## The User Model

We have one model for both the *guests* and the *hosts* who are using ***Airtng***.

When ***Airtng*** takes off it will merit creating two more classes that inherit from the base `User` class. Since we're still on the ground this should suit us fine (*to boldly stay...*).

First, we validate the 'uniqueness' of our user - they should be unique, just like everyone else. Specifically, it is important we ensure that the `phone_number` attribute is unique since we will use this to look up `User` records on incoming SMSes.

After that, we set up our associations for when we need to query for reservations.

```rb title="Validators and relationships of the User Model" description="app/models/user.rb"
# !mark(1:11)
class User < ActiveRecord::Base
  has_secure_password

  validates :email,  presence: true, format: { with: /\A.+@.+$\Z/ }, uniqueness: true
  validates :name, presence: true
  validates :country_code, presence: true
  validates :phone_number, presence: true, uniqueness: true
  validates_length_of :password, in: 6..20, on: :create

  has_many :vacation_properties
  has_many :reservations, through: :vacation_properties

  def send_message_via_sms(message)
    @app_number = ENV['TWILIO_NUMBER']
    @client = Twilio::REST::Client.new ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']
    phone_number = "+#{country_code}#{self.phone_number}"
    sms_message = @client.account.messages.create(
      from: @app_number,
      to: phone_number,
      body: message,
    )
  end

  def check_for_reservations_pending
    if pending_reservation
      pending_reservation.notify_host(true)
    end
  end

  def pending_reservation
    self.reservations.where(status: "pending").first
  end

  def pending_reservations
    self.reservations.where(status: "pending")
  end

end
```

Arguably the most important task delegated to our `User` model is to send an SMS to the user when our app requests it, let's take a look.

## Send Messages Into the Void

Since we only send text messages in our application when we're communicating with specific users, it makes sense to create this function on the `User` class. And yes: these 7 lines are all you need to send SMSes with Ruby and Twilio! It's really just two steps:

1. We look up our app's phone number.
2. We initiate our Twilio client and build the message.

Now whenever we need to communicate with a user, whether *host* or *guest*, we can pass a message to this user method and... *Voilà!* We've sent them a text.

(If you peek below this method you'll see the helper methods for finding `pending_reservations` that we mentioned previously.)

```rb title="Use Twilio client to send an SMS" description="app/models/user.rb"
# !mark(13:41)
class User < ActiveRecord::Base
  has_secure_password

  validates :email,  presence: true, format: { with: /\A.+@.+$\Z/ }, uniqueness: true
  validates :name, presence: true
  validates :country_code, presence: true
  validates :phone_number, presence: true, uniqueness: true
  validates_length_of :password, in: 6..20, on: :create

  has_many :vacation_properties
  has_many :reservations, through: :vacation_properties

  def send_message_via_sms(message)
    @app_number = ENV['TWILIO_NUMBER']
    @client = Twilio::REST::Client.new ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']
    phone_number = "+#{country_code}#{self.phone_number}"
    sms_message = @client.account.messages.create(
      from: @app_number,
      to: phone_number,
      body: message,
    )
  end

  def check_for_reservations_pending
    if pending_reservation
      pending_reservation.notify_host(true)
    end
  end

  def pending_reservation
    self.reservations.where(status: "pending").first
  end

  def pending_reservations
    self.reservations.where(status: "pending")
  end

end
```

Now we need a way to handle incoming texts so our lucky host can accept or reject a request. Let's look there next.

## Handle Incoming Messages

The `accept_or_reject` controller handles our incoming Twilio request and does three things:

1. Check for a pending reservation the user owns
2. Update the status of the reservation
3. Respond to the host (and guest)

## Incoming Twilio Request

An incoming request from Twilio comes with some helpful parameters including the `From` phone number and the message `Body`.

We'll use the `From` parameter to lookup the *host* and check if she has any pending reservations. If she does, we'll use the message body to check if she accepted or rejected the reservation.

Then we'll redirect the request to a TwiML response to send a message back to the user.

## TwiML Response

Usually a Rails controller has a template associated with it that renders a webpage. In our case, the only request being made will be by Twilio's API so we don't need a public page. Instead we're using Twilio's Ruby API to render a custom TwiML response as raw XML on the page.

```rb title="Webhook for handling Host's decision" description="app/controllers/reservations_controller.rb"
# !mark(21:59)
class ReservationsController < ApplicationController

  # GET /vacation_properties/new
  def new
    @reservation = Reservation.new
  end

  def create
    @vacation_property = VacationProperty.find(params[:reservation][:property_id])
    @reservation = @vacation_property.reservations.create(reservation_params)

    if @reservation.save
      flash[:notice] = "Sending your reservation request now."
      @reservation.notify_host
      redirect_to @vacation_property
    else
      flast[:danger] = @reservation.errors
    end
  end

  # webhook for twilio incoming message from host
  def accept_or_reject
    incoming = Sanitize.clean(params[:From]).gsub(/^\+\d/, '')
    sms_input = params[:Body].downcase
    begin
      @host = User.find_by(phone_number: incoming)
      @reservation = @host.pending_reservation

      if sms_input == "accept" || sms_input == "yes"
        @reservation.confirm!
      else
        @reservation.reject!
      end

      @host.check_for_reservations_pending

      sms_reponse = "You have successfully #{@reservation.status} the reservation."
      respond(sms_reponse)
    rescue
      sms_reponse = "Sorry, it looks like you don't have any reservations to respond to."
      respond(sms_reponse)
    end
  end

  private
    # Send an SMS back to the Subscriber
    def respond(message)
      response = Twilio::TwiML::Response.new do |r|
        r.Message message
      end
      render text: response.text
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def reservation_params
      params.require(:reservation).permit(:name, :phone_number, :message)
    end

end
```

Next up, let's see how to notify the guest.

## Notify the Guest

The final step in our workflow is to notify the *guest* that their reservation has been booked (or, ahem, rejected).

We called this method earlier from the `reservations_controller` when we updated the reservation status. Here's what it does:

* We lookup the guest with the `reservation.phone_number`
* If the status was changed to an expected result we notify the guest of the change.

And of course all we need to do to send the SMS message to the guest is call the `send_message_via_sms` method that is present on all users.

```rb title="Let the guest have the host's decision" description="app/models/reservation.rb"
# !mark(37:44)
class Reservation < ActiveRecord::Base
  validates :name, presence: true
  validates :phone_number, presence: true

  enum status: [ :pending, :confirmed, :rejected ]

  belongs_to :vacation_property
  belongs_to :user

  def notify_host(force = true)
    @host = User.find(self.vacation_property[:user_id])

    # Don't send the message if we have more than one or we aren't being forced
    if @host.pending_reservations.length > 1 or !force
      return
    else
      message = "You have a new reservation request from #{self.name} for #{self.vacation_property.description}:

      '#{self.message}'

      Reply [accept] or [reject]."

      @host.send_message_via_sms(message)
    end
  end

  def confirm!
    self.status = "confirmed"
    self.save!
  end

  def reject!
    self.status = "rejected"
    self.save!
  end

  def notify_guest
    @guest = User.find_by(phone_number: self.phone_number)

    if self.status_changed? && (self.status == "confirmed" || self.status == "rejected")
      message = "Your recent request to stay at #{self.vacation_property.description} was #{self.status}."
      @guest.send_message_via_sms(message)
    end
  end
end
```

Thank you so much for your help! Airtng now has a nice SMS based workflow in place and you're ready to add a workflow to your own application.

Let's look at some other features you might enjoy adding for your use cases.

## Where to Next?

Ruby and Rails and Twilio: what an excellent combo. Here are a couple other ideas you might pursue:

**[Masked Phone Numbers](/docs/messaging/tutorials/masked-numbers/ruby)**

Protect your users' privacy by anonymously connecting them with Twilio Voice and SMS.

**[Automated Survey](/blog/automated-survey-ruby-rails)**

Collect instant feedback from your customers with SMS or Voice.
