Anyone living in NYC with a car knows the pain that is Alternate Side Parking rules. There’s the need to always to be conscious of which side of the street your car is parked on, to run out at the most inopportune times to move it to the other side of the street, and occasionally the opportunity to be blocked in by someone double-parking. All of these, and more, are part of what New York drivers have to put up with on a weekly cycle.
To help me deal with it, I created @AlterSideBot, a Twitter Bot that retweets whenever Alternate Side Parking rules are suspended so I know I don’t have to worry on those days.
The idea hit on and followers kept coming in… but it had its limitations. First of all, most people aren’t glued to their Twitter feeds, and one tweet can easily get lost in the noise. Not to mention that many people don’t even use Twitter (or use it rarely enough) so my bot wouldn’t be helpful to them at all.
For this reason, I decided to add SMS functionality to my Twitter bot, so people could subscribe to receive SMS notifications to their phones.
The app was a fantastic success; within a few days I had over 100 subscribers. I had a lot of fun making it, so I figured I’d write up the process.
For the sake of this tutorial I won’t go into the making of the Twitter bot (that’s a whole blog post in itself), instead, we will create a much simpler app where people can subscribe to receive cat facts.
What you’ll need
- A computer running MacOS or Linux (if you’re using Windows 10 you can follow this guide to install Rails)
- Rails version 5 or higher
- A Twilio account (you can sign up for a free trial here) with a phone number that can send/receive SMS’s
- A telephone that can send/receive SMS messages so you can test your app out
Building the Rails Cat Facts app
Getting started
Let’s start by creating our Rails app. In your terminal run rails new cat_facts
. This will generate a template for a basic Rails app called Cat Facts. When your terminal finishes doing what it’s doing type cd cat_facts
to go into the root directory of your app.
Open the newly created cat_facts
directory in your favorite editor, and let’s get going.
Give me the facts
We will start by creating our CatFact
model, the nerve engine of our app.
In your terminal run rails generate model cat_fact fact:string
. This should generate a CatFact
model that has a fact
attribute where we will store our facts, as well as a migration that should look like this:
class CreateCatFacts < ActiveRecord::Migration[5.1]
def change
create_table :cat_facts do |t|
t.string :fact
t.timestamps
end
end
end
Double check that everything looks the way it should and run rails db:migrate
to create the database table.
Now let’s generate the controller by running rails generate controller cat_facts index create
this will generate a CatFactsController
with an index
and a create
action.
The controller will have also created routes for our app at GET cat_facts/index
and GET cat_facts/create
. That’s more complex than we need, let’s go to config/routes.rb
and replace the contents of that file with the following:
Rails.application.routes.draw do
resources :cat_facts, only: [:create, :index]
end
That will give us a route at GET /cat_facts
where we will display all the facts and a POST /cat_facts
route we can use to create new Cat Facts.
An App with a View
Let us now build out our view. Put the following in app/views/cat_facts/index.html.erb
:
<h1>Cat Facts</h1>
<p>Add a new cat fact:</p>
<%= form_for @cat_fact do |f| %>
<%= f.label :fact %><br>
<%= f.text_area :fact %><br>
<%= f.submit %>
<% end %>
This will give us a form to create new Cat Facts.
Under that put the following:
<h3>Previous Facts:</h3>
<ul>
<% @cat_facts.each do |cat_fact| %>
<li><%= cat_fact.fact %></li>
<% end %>
</ul>
This will let us see a list of all the facts we created.
Let us now build out our controller. In app/controllers/cat_facts_controller.rb
edit the index
method to look like the following:
def index
@cat_fact = CatFact.new
@cat_facts = CatFact.all
end
And the create
method as follows:
def create
@cat_fact = CatFact.new
@cat_fact.fact = params[:cat_fact][:fact]
@cat_fact.save
redirect_to cat_facts_path
end
We can now test this out. Run rails server
in your terminal. Open up a browser and go to http://localhost:3000/cat_facts, you should see a form for new cat facts. If you put in a fact and hit submit you should see it appear below the form.
Adding SMS notifications
Now that we have the base of our app working we can connect it to our Twilio account and let people subscribe to receive notifications.
Configure our app
First let’s add the twilio-ruby gem so our app can talk to Twilio. In your Gemfile add the following line: gem 'twilio-ruby'
and then run bundle install
in your terminal.
Next, we will have to give our app your Twilio credentials. Log in to your Twilio account and go to the console. At the top left, you will see your Account SID and Auth Token, take note of them for the next step.
In your app, create a file in config/initializers
called twilio.rb
and paste the following code:
Twilio.configure do |config|
config.account_sid = "ACCOUNT_SID"
config.auth_token = "AUTH_TOKEN"
end
Make sure you replace ACCOUNT_SID
and AUTH_TOKEN
with the Account SID and Auth token you saved in the last step (if you are planning on uploading the code to GitHub or the like do not commit your token and SID, instead consider using environment variables).
Next, let’s set up a route for Twilio to interact with our app. In your terminal type: rails generate controller twilio sms
. This will give our app a route at get '/twilio/sms'
along with a corresponding TwilioController
with an sms
action.
In config/routes.rb
let’s change the get '/twilio/sms'
route to post '/twilio/sms'
.
Configure Twilio
Now we need to tell Twilio where to find your app, but in order to do that we need to expose our app to the internet. We will do that using ngrok.
To check if you already have ngrok installed on your computer type ngrok help
in your terminal. If a list of commands shows up in the terminal you are good to go, if you got an error ngrok: command not found
you will need to download and install ngrok here.
Once you have ngrok installed open up another terminal and type ngrok http 3000
. You should see something similar to this in your terminal:
This means that ngrok opened up a window to port 3000 on your localhost and exposed it to the internet at the URL it shows by “Forwarding” (which will be different than the one in the screenshot above).
Now go back to your Twilio Dashboard and click on “Manage Numbers” in the Phone Numbers section, and then click on the phone number you will be connecting to our app.
Under the Messaging section, by “A Message Comes In” choose “Webhook”. In the field next to Webhook put in the URL ngrok gave you followed by /twilio/sms
(so it should look like https://ngrokurl.ngrok.io/twilio/sms
replacing ngrokurl with the URL in your terminal) and choose “HTTP POST” then hit “Save”.
Now Twilio will forward any incoming messages to the ngrok route we specified, which will in turn forward it to our localhost at port 3000. Let’s configure our app to respond.
TwiML-dee TwiML-dum
The Twilio API communicates using TwiML (which stands for Twilio Markup Language. A markup scheme similar to XML) using verbs like <Message> to send text messages and <Say> to send voice. Thankfully, the Twilio gem we installed will handle creating the TwiML for us using the Twilio::TwiML class. Let’s use it to create our app’s responses.
In app/controllers/twilio_controller.rb
let’s put the following code in the TwilioController
:
class TwilioController < ApplicationController
skip_before_action :verify_authenticity_token
def sms
body = helpers.parse_sms(params)
response = Twilio::TwiML::MessagingResponse.new do |r|
r.message body: body
end
render xml: response.to_s
end
end
What that does is set a variable response to a TwiML Response object that contains the text “Hello World” in the body. We then render that response object as XML. The result is an XML response that looks like this:
<?xml version=”1.0” encoding=”UTF-8”?>
<Response>
<Message>Hello World</Message>
</Response>
Let’s test it out; type rails server
in your console (if you still have your rails server running from earlier shut it down and restart it so our latest changes take effect), then send an SMS to your Twilio number. You should receive a text message in response with the text “Hello World”. Neat no?
(Confession: seeing my phone light up for the first time with a text message I had sent using code was one of those moments that reminded me why I fell in love with coding in the first place!)
It’s all about the cats!
Of course, as cool as that was, we are building a Cat Facts app, not a Hello World app, so let’s change our TwiML object to return content that’s a bit more dynamic.
Let’s change our response object to look as follows:
response = Twilio::TwiML::MessagingResponse.new do |r|
r.message body: CatFact.last.fact
end
If you send an SMS to your Twilio number now, you should receive a response with the latest Cat Fact you added to your app.
We now have an app that users can text whenever they want and receive the latest cat facts in response. How cool is that?!
Subscribe!
But our users want more; they don’t want to have to keep texting us in the hope that we added a new fact, they want to be able to subscribe to receive notifications as soon as new cat facts come out!
To do that we will need to add some logic to our app so it can parse incoming text messages and respond accordingly.
We could put all that logic in our sms
controller action, but that would make for a pretty fat controller. Instead, we will put it in a helper method.
But before we get there, we need to have a way of storing our subscribers in our database.
In your terminal type: rails generate model subscriber phone_number
. That will give us a Subscriber
model with a phone_number attribute. Run rails db:migrate
in your terminal to add that table to our database.
Now we can build a helper method that will help us parse users’ text messages so that they can subscribe to our app.
Let’s think of what we want our users to be able to do.
Users should be able to:
- Send a message SUBSCRIBE to subscribe to our app
- Send FACT if they want to get the latest cat fact
- Send UNSUBSCRIBE if they want to stop receiving notifications
(who would want to stop getting cat facts? I know! That said, please don’t make a subscription-based app where users can’t unsubscribe easily)
Parsing messages
To do that we will build a method that can analyze the incoming text message, see what it says, and return an appropriate response.
Go to app/helpers/twilio_helper.rb
and put the following method in the TwilioHelper
Module:
def parse_sms(sms)
body = sms[:Body]&.strip&.upcase
from = sms[:From]
case body
when "SUBSCRIBE"
subscriber = Subscriber.create(phone_number: from)
return "The number #{from} has been subscribed to receive cat facts. Text UNSUBSCRIBE at any time to unsubscribe."
when "UNSUBSCRIBE"
subscriber = Subscriber.find_by(phone_number: from)
if subscriber
subscriber.destroy
return "The number #{from} has been unsubscribed. Text SUBSCRIBE at any time to resubscribe."
else
return "Sorry, I did not find a subscriber with the number #{from}."
end
when "FACT"
return CatFact.last.fact
else
return "Sorry I didn’t get that. the available commands are SUBSCRIBE, UNSUBSCRIBE, and FACT."
end
end
What that does is define a parse_sms
method that takes in an SMS as an argument (really just the params hash twilio sent along with the GET request). The method then passes the body of the SMS to a switch statement that checks it. If the body of the SMS says SUBSCRIBE it will subscribe the incoming number, if it says UNSUBSCRIBE it will unsubscribe that number, and if it says FACT it will return the latest cat fact.
In each case, the method returns a helpful message. If the incoming text didn’t match any of those, a helpful message is returned with the available commands.
Now let’s update our sms controller as follows:
def sms
body = helpers.parse_sms(params)
response = Twilio::TwiML::MessagingResponse.new do |r|
r.message body: body
end
render xml: response.to_s
end
What we’re doing is we’re taking the incoming params and passing them to the parse_sms
method we defined earlier.
We then take the string we got back from parse_sms
and create a TwiML response object using the response as the body. We then convert the TwiML object to XML and send it back to Twilio.
But does it work?
Let’s test it out. Fire up your rails server
again if you shut it down and text SUBSCRIBE to your Twilio number. You should soon receive a response that your number has been subscribed. If you now go to your rails console and run Subscriber.last.phone_number
your phone number should come up.
Almost done!
The only thing we have left now is to set our app up to send notifications to our subscribers whenever we add a new cat fact.
Let’s go to app/models/cat_fact.rb
and in our CatFact
class let’s add the following method:
def notify_subscribers
client = Twilio::REST::Client.new
Subscriber.find_each do |subscriber|
client.messages.create(
from: "YOUR_TWILIO_PHONE_NUMBER",
to: subscriber.phone_number,
body: self.fact
)
end
end
That method iterates through all of our subscribers and uses the Twilio REST API to send each of them a text message with the given cat fact as the body (don’t forget to replace YOUR_TWILIO_PHONE_NUMBER
with your actual Twilio number using the format “+19876543210”
).
All we have to do now is tell our CatFact
class to call this method every time a new CatFact
is created. Add the following near the top of your CatFact
class:
after_create :notify_subscribers
Now every time an instance of CatFact
is created the notify_subscribers
method will be called, and all of our subscribers will be notified.
Putting it all together
We are now ready to test out our app and see if it works as intended.
- Fire up your rails server (if it isn’t still running)
- In your browser navigate to http://localhost:3000/cat_facts
- Fill out the form with a new cat fact and hit Create
- You (along with anyone else who subscribed to your app) should receive a text message with your new fact!
Conclusion
That was really cool, feel free to play around with it and see what other features you can add (for example, in my Alternate Side Parking bot users can call in to get the latest status as well).
The Twilio documentation is very friendly and easy to use, they even have a game called TwilioQuest to help you get started!
If you want to see the code for the app we just built, you can check out the repo on GitHub.
And as a final reminder, if you live in NYC feel free to follow @AlterSideBot or text SUBSCRIBE to 347–404–5618 to get SMS notifications whenever Alternate Side Parking rules are suspended.
If you liked that you can read more by Yechiel Kalmenson on his blog Rabbi On Rails or follow him on Twitter @yechielk