Learning To Code

Join me as I take the plunge!

Make Your Own URL Shortener With Rails 4 and Heroku

Ever wanted to make your very own URL shortener? Know a little bit of rails? Then you’re good to go!

My goal was to make my own personal bit.ly. I wanted a home page with a text box that I could paste in a URL, press a button, and get a shortened link back. I also wanted to track the number of times each of my links was clicked. I also wanted a very visual leaderboard of my top links as well as a tabular page of all my links. I also wanted to deploy the app live to the internet and use my own custom short domain.

Here is what I ended up making: acal.io

Set Up

The very first step was to a create new rails app with postgres since I knew that I would later be deploying to heroku

1
rails new url-shortener --database=postgresql

The next thing I do to any new rails app is to add pry to gemfile. Pry is an amazing gem that really helps me develop. If you are not sure about using pry, or how it can help, you I suggest you watch this talk about REPL Driven Development from Ruby Conf 2013.

1
gem 'pry'

I knew that I wanted the app to look at least somewhat nice, so not being a designer I defaulted to using bootstrap. I download the latest copy of bootstrap and unzipped it.

I added the file ‘bootstrap.min.js’ to the folder vendor/assets/javascripts. Then modified application.js to indicate this

1
//= require bootstrap.min

I add the file ‘bootstrap.min.css’ to the folder vendor/assets/stylesheets. Then I modified application.css to indicate this

1
 *= require bootstrap.min

Models

Now I reached the ‘heart’ of the application - the link model. I first generated the scaffolding for the link

1
rails g scaffold link given_url:string slug:string clicks:integer snapshot:string title:string

I then went into the actual migration file and modified the default value for clicks BEFORE I migrated. This is because you can’t increment a non-integer value (nil), which is the original default.

1
t.integer :clicks,  :default => 0

I then migrated the database

1
rake db:migrate

Now that I had my model in place, I still needed to actually generate a short link for each URL that I pasted in.

I made a method ‘generate_slug’, which creates the short part of the link after the base url. All this method does is take the unique id for a link, and converts it into it’s Base 36 equivalent. Base 36 allows really large numbers to be shown in just a few characters. For example the 1 Billionth link in the database will have a slug of “gjdgxs” - just 6 characters as opposed to the 10 characters in the number’s ID (1,000,000,000).

1
2
3
4
  def generate_slug
    self.slug = self.id.to_s(36)
    self.save
  end

But the slug alone is not enough. I want to show the user the entire full URL. To do this I want to create an environmental variable, and knowing that I will be storing some keys down the line anyway, I want to keep it just to my machine. I added the figaro gem to accomplish this, but there are other ways to do this, which I’ve written about before.

1
gem "figaro"

I then used figaro’s generate command to create the application.yml file that git will ignore (so your passwords don’t get committed to the rest of the world)

1
rails generate figaro:install

Then I add the base URL as an environmental variable to the application.yml file

1
BASE_URL: 'http://acal.io/'

With this I was able to make a convenience method to display the entire shortened URL

1
2
3
  def display_slug
    ENV['BASE_URL'] + self.slug
  end

In order to make the trending links section really pop visually, I figured getting a screenshot would do the trick. I also wanted to grab the title of each page, in order to give a bit more context to the end user. I used IMGKit to capture the screenshot, CarrierWave and Fog to associate a screenshot with a model and upload it to Amazon S3, and Mechanize for getting the page title. I also knew upfront that I want to do this asynchronously, so I’ll also threw in SideKiq.

1
2
3
4
5
gem 'imgkit'
gem 'carrierwave'
gem "fog", "~> 1.3.1"
gem 'mechanize'
gem 'sidekiq'

I got my key’s from Amazon, and created a bucket on S3. I went back to my application.yml file and added these all as environmental variables, so I could call them elsewhere in the application.

1
2
3
AWS_ACCESS_KEY_ID: 'REDACTED'
AWS_SECRET_ACCESS_KEY: 'REDACTED'
AWS_BUCKET: 'REDACTED'

First I made a method for actually getting the screenshot and page title. For now, these are just Sidekiq Workers which I’ll actually build later. Of note is that I had to pass in the ID of a link, and not the full object, in order to get SideKiq to work.

1
2
3
4
  def screenshot_scrape
    Screenshot.perform_async(self.id)
    Scrape.perform_async(self.id)
  end

I already had a method generate a slug, and now I had a method to both capture the screenshot and capture the page title. I decided to use after_create callbacks to make sure these things all happend after each new link was created.

1
  after_create :generate_slug, :screenshot_scrape

I then made a screenshot worker, which captures a screenshot of each link. Because I had to pass in an ID, I first had to lookup the object. I then needed to create a temporary file of the screenshot as an intermediate step, to ensure it worked with CarrierWave. Finally I associated the screenshot with the link, which then allows triggers a call to Fog to upload the screenshot to S3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Screenshot
  include Sidekiq::Worker

  def perform(link_id)
    link = Link.find(link_id)
    file = Tempfile.new(["template_#{link.id.to_s}", '.jpg'], 'tmp', :encoding => 'ascii-8bit')
    file.write(IMGKit.new(link.given_url, quality: 50, width: 600).to_jpg)
    file.flush
    link.snapshot = file
    link.save
    file.unlink
  end

end

Then I made a scrape worker, which scrapes the link and finds the page title.

1
2
3
4
5
6
7
8
9
10
11
12
class Scrape
  include Sidekiq::Worker

  def perform(link_id)
    link = Link.find(link_id)
    agent = Mechanize.new
    page = agent.get(link.given_url)
    link.title = page.title
    link.save
  end

end

Next I made a configuration file for CarrierWave, and made sure it would work on Heroku (which has permissions issues - more on that later)

1
2
3
4
5
6
7
8
9
10
11
CarrierWave.configure do |config|
  config.root = Rails.root.join('tmp')
  config.cache_dir = 'carrierwave'

  config.fog_credentials = {
    :provider               => 'AWS',
    :aws_access_key_id      => ENV['AWS_ACCESS_KEY_ID'],
    :aws_secret_access_key  => ENV['AWS_SECRET_ACCESS_KEY']
  }
  config.fog_directory  = ENV['AWS_BUCKET']
end

I then made the uploader that CarrierWave needs. the store_dir method tells S3 the naming convention of the screenshots, and I just left it at the default. The cache_dir method is a way to get around Heroku’s file writing permissions issues. Basically Heroku won’t let you write files except in this one place, so I made sure to do this.

1
2
3
4
5
6
7
8
9
10
11
class SnapshotUploader < CarrierWave::Uploader::Base
  storage :file
  storage :fog
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  def cache_dir
    "#{Rails.root}/tmp/uploads"
  end
end

I finished up by adding the mount_uploader call to the link model.

1
  mount_uploader :snapshot, SnapshotUploader

Controllers

First up I make some modifications to the auto generated links controller. I decided to delete the index, edit, update, and destroy actions. These actions get into permissions issues, which could lead naturally to building out users and all sorts of other features that are not core to this app. I’m not a fan of feature creep, and recommend that you also limit the scope of your own projects whenever possible. I also knew that I wanted the create action to respond asynchronously. I didn’t want to have to do a hard page reload when you shortened the link, and like bit.ly does, it just appends your new short link back to the screen. To do this I added the ability for the create action to respond to format.js (more on this later).

I also added the ability to route a slug on a naked url to the show action. This is so the URL http://acal.io/slug will naturally get routed to the long URL that the corresponding slug was made for. It also increments every time the link is clicked. If the user instead goes to http://acal.io/links/id, no incrementing is done, and they are taken to the show page for that link - same as normal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class LinksController < ApplicationController
  before_action :set_link, only: [:show]

  def index
    @links = Link.all
  end

  def show
    if params[:slug]
      @link = Link.find_by(slug: params[:slug])
      if redirect_to @link.given_url
        @link.clicks += 1
        @link.save
      end
    else
      @link = Link.find(params[:id])
    end
  end

  def create
    @link = Link.new(link_params)

    respond_to do |format|
      if @link.save
        format.html { redirect_to root_path, notice: 'Link was successfully created.' }
        format.js
        format.json { render action: 'show', status: :created, location: @link }
      else
        format.html { render action: 'new' }
        format.json { render json: @link.errors, status: :unprocessable_entity }
      end
    end
  end

  private
    def set_link
      @link = Link.find_by(slug: params[:slug])
    end

    def link_params
      params.require(:link).permit(:given_url)
    end
end

I then updated the routes file to map the naked url with a slug to the show action.

1
  get ':slug' => 'links#show'

I generally make a home controller for my apps, and it’s a common design pattern I’ve seen others do, so I generated a home controller.

1
rails g controller home

I modified the routes file to make the index action on the home controller the root

1
  root 'home#index'

I decided to only have the top 12 trending items, since it’s allows for bootstrap to gracefully degrade from 4 to 3 to 2 to 1 columns.

1
2
3
4
  def index
    @link = Link.new
    @top_links = Link.order(clicks: :desc).first(12)
  end

I also wanted a page that just listed all the links created as a table, so first made a route.

1
  get '/all' => 'home#all'

I wanted to be sure not to overwhelm the server in case this app received a lot of link shortening requests. I figured pagination would do the trick and I opted to use the will_paginate gem.

1
gem 'will_paginate', '~> 3.0'

Then I filled out the all action, and decided to just put 4 rows per page as the app is still small.

1
2
3
4
  def all
    @link = Link.new
    @links = Link.paginate(:page => params[:page], :per_page => 4)
  end

Front End

I first made a new partial ‘layouts/_new_link’ for a new link that would persist on top of all the pages in the app. I included a blank div called ‘result’ so I could append the result later on.

1
2
3
4
5
6
7
<div class="row">

<%= render 'links/form' %>
</div>

<div class="result">
</div>

I then modified the ‘layout/application’ file. I changed the auto generated title, added the new partial I just created and wrapped everything in a bootstrap container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
  <title>Url Shortener</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>
  <div class="container">
    <%= render 'layouts/new_link' %>
    <%= yield %>
  </div>
</body>
</html>

I then modified the link form partial ‘links/_form’. I wanted to make this submit asynchronously so added a remote:true option to the form. I also added a logo, some placeholder text, and some bootstrap styling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%= form_for(@link, remote: true) do |f| %>
  <% if @link.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@link.errors.count, "error") %> prohibited this link from being saved:</h2>

      <ul>
      <% @link.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  <div>
    <%= link_to image_tag("logo.png"), root_path %>
  </div>
  <div class="field">
    <%= f.text_field :given_url, :class => "form-control", :placeholder => "Paste a link to shorten" %>
  </div>
  <div class="actions">
    <%= f.submit 'Shorten Me', :class => "btn btn-primary"%>
  </div>
<% end %>

I then wanted to append the short link result back onto the page once it was processed. I referred to this earlier in the post when I was making the links controller. In order to do this I made a new file called create.js.erb in the ‘views/links’ folder.

1
$('.result').append('<p class="short_url">Your Shortened Link: <%= link_to @link.display_slug, @link.display_slug, target: "_blank" %></p>')

I then made the ‘links/show’ page. I added in a bit of formatting, both from bootstrap, and from my own css.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<p id="notice"><%= notice %></p>

<% if @link.snapshot %>
<div class="screenshot-outer-individual"><%= link_to image_tag(@link.snapshot.url, class: "screenshot"), @link %></div>
<% end %>
<p class="title"><%= @link.title %></p>
<p class="short_url"><%= link_to @link.display_slug, @link.display_slug, target: '_blank' %></p>
<ul class="click_time">
  <li><span class="badge"><span class="clicks"><%= @link.clicks %></span></span> clicks</li>
  <li><span class="time"><%= time_ago_in_words(@link.created_at) %> ago</span></li>
</ul>
<p class="long_url"><%= link_to @link.given_url, @link.given_url %></p>

<%= link_to 'Back', root_path %>

I also wanted to make sure that non-valid URLs could not be submitted. There are a lot of ways to do this, but I settled with doing client side validation.

I first installed the jquery-validation-rails gem, which is just an easy way to get the JQuery Validation Plugin into a rails app.

1
gem 'jquery-validation-rails'

I then indicated that these files were part of my application in application.js

1
2
//= require jquery.validate
//= require jquery.validate.additional-methods

I then made the actual javascript validation that required both the presence of a URL, and it’s validity as a URL according to a regular expression. I put this in ‘application.js’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$.validator.addMethod(
        "regex",
        function(value, element, regexp) {
            var re = new RegExp(regexp);
            return this.optional(element) || re.test(value);
        },
        "Please enter a valid URL (using http)"
);

$('#new_link').ready(function() {
  var password_validator = $('#new_link').validate({
    rules: {
      'link[given_url]': {
        required: true,
        regex: "^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}"
      },
    },
    messages: {
      'link[given_url]': {
        required: 'Please put in a URL',
      }
    }
  });
});

I then made the ‘home/index’ page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="title">
  <h1>Trending links</h1>
</div>

<div class="row">
  <% @top_links.each do |link| %>
    <div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 link">
      <% if link.snapshot %>
      <div class="screenshot-outer"><%= link_to image_tag(link.snapshot.url, class: "screenshot"), link %></div>
      <% end %>
      <p class="title"><%= truncate(link.title, length: 60) %></p>
      <p class="short_url"><%= link_to link.display_slug, link.slug, target: '_blank' %></p>
      <ul class="click_time">
        <li><span class="badge"><span class="clicks"><%= link.clicks %></span></span> clicks</li>
        <li><span class="time"><%= time_ago_in_words(link.created_at) %> ago</span></li>
      </ul>
      <p class="long_url"><%= link_to truncate(link.given_url, length: 60), link.given_url %></p>
    </div>
  <% end %>
</div>

<p class="bottom_nav"><%= link_to 'See all of the links', '/all' %></p>

I finally made the ‘home/all’ page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div class="table-responsive">
  <table class="table">
    <thead>
      <tr>
        <th>Given url</th>
        <th>Short Link</th>
        <th>Title</th>
        <th>Clicks</th>
        <th></th>
        <th></th>
      </tr>
    </thead>

    <tbody>
      <% @links.each do |link| %>
        <tr>
          <td><%= link.given_url %></td>
          <td><%= link_to link.display_slug, link.slug, target: '_blank' %></td>
          <td><%= link.title %></td>
          <td><%= link.clicks %></td>
          <td><%= link_to 'Show', link %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>



<%= will_paginate @links %>

<p class="bottom_nav"><%= link_to 'See just the trending links', root_path %></p>

Along the way I added a lot of custom css to ‘application.css’. Of note is an animated yellow flash of the new shortened link as it is appended to the page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require_self
 *= require bootstrap.min
 *= require_tree .
 */
body
{
background-color:yellow;
padding-bottom: 100px;
}

span.clicks {
  font-size: 50px;
}

span.time {
  font-style: italic;
}

img.screenshot {
  width:100%;
  /*position: absolute;*/
  /*clip: rect(0px,200px,300px,0px);*/
}

form div {
  /*float:left;*/
  display:inline-block;
}

form {
  text-align: center;
  margin-top:20px;
}
form div.field {
  margin-top:12px;
  margin-left:10px;
}

form div.actions {
  margin-top:12px;
  margin-left:10px;
}

h1 {
  text-align: center;
}
div.title {
  margin-bottom: 20px;
}
div.result {
  height: 60px;
}

@keyframes myfirst
{
  from {background: yellow;}
  to {background: none;}
}

@-webkit-keyframes myfirst /* Safari and Chrome */
{
  from {background: yellow;}
  to {background: none;}
}

div.result p {
  margin:0;
  padding:0;
  line-height: 60px;
  animation: myfirst 1s;
  -webkit-animation: myfirst 1s; /* Safari and Chrome */
}

div.link {
  margin-bottom: 60px;
}


p.bottom_nav {
  text-align: center;
}

p.short_url {
  text-align: center;
  font-size: 20px;
  margin-top:10px;
}

p.title {
  text-align: center;
  height: 36px;
}

p.long_url {
  text-align: center;
  color:#DCDCDC;
  font-size: 10px;
  padding-left: 10px;
  padding-right: 10px;
  overflow: hidden;
  height: 36px;
}

ul {
  padding:0px;
  list-style:none;
  height:400px;
}

ul.click_time li:first-child{
  float:left;
}

ul.click_time li:last-child{
  margin-top:55px;
  float:right;
}

ul.click_time {
  height:70px;
  margin-left: 20px;
  margin-right: 20px;
}

li {
  margin-top:20px;
}

div.screenshot-outer {
  height:200px;
  overflow: hidden;
  border:1px solid;
  border-radius:5px;
  border-color:#DCDCDC;
}

div.screenshot-outer-individual {
  height:340px;
  overflow: hidden;
  border:1px solid;
  border-radius:5px;
  border-color:#DCDCDC;
}

div.screenshot-outer img {
  padding:10px;
}

Deploying to Heroku

Heroku makes deploying rails apps incredibly easy, and I use it for all my side projects. I first had to add the ‘rails_12factor’ gem and a line indicating that I was using ruby 2.0.0. In order to get SideKiq to work I had to use unicorn, so included the ‘unicorn’ gem too.

1
2
3
gem 'rails_12factor', group: :production
gem 'unicorn'
ruby "2.0.0"

Heroku does not play nicely with the IMGKit gem, and in order to make the screenshots work I needed to download a compiled version of IMGKit.

https://wkhtmltopdf.googlecode.com/files/wkhtmltoimage-0.10.0_rc2-static-amd64.tar.bz2

I unzipped it and put it in the ‘bin’ directory of my application. I then made a configure file for IMGKit so that this would actually take effect.

1
2
3
IMGKit.configure do |config|
  config.wkhtmltoimage = Rails.root.join('bin', 'wkhtmltoimage-amd64').to_s if ENV['RACK_ENV'] == 'production'
end

I also had to modify config.ru to get CarrierWave to work. This again was a permissions issue with Heroku not allowing file write access, except for the one tmp directory.

1
2
3
4
5
# This file is used by Rack-based servers to start the application.

require ::File.expand_path('../config/environment',  __FILE__)
use Rack::Static, :urls => ['/carrierwave'], :root => 'tmp'
run Rails.application

I then made a Procfile, which is all Heroku needs to run redis and SideKiq for your application automatically. I put it in the base directory (same place as the gemfile).

1
2
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
worker: bundle exec sidekiq

I then had to make a configure file for unicorn.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

I was finally ready to go live! I created, pushed, and migrated the app on Heroku

1
2
3
heroku create
git push heroku master
heroku run rake db:migrate

I then configured the environmental variables on Heroku (remember, these are in application.yml which is ignored by git and thus not already on Heroku).

1
2
3
4
heroku config:set AWS_ACCESS_KEY_ID=REDACTED
heroku config:set AWS_SECRET_ACCESS_KEY=REDACTED
heroku config:set AWS_BUCKET=REDACTED
heroku config:set BASE_URL=http://acal.io/

Then I added redistogo (free) and added another dyno so that SideKiq would run.

1
2
heroku addons:add redistogo
heroku ps:scale worker+1

Finally I bought a custom domain through hover, who I try to use for all my domain purchasing needs (no affiliation, they are just awesome). I had to add a CNAME record called ‘www’ with the target being ‘cryptic-shore-8548’, since my heroku domain is https://cryptic-shore-8548.herokuapp.com.

I also set up domain forwarding so that a naked url (http://acal.io) would just redirect to www (http://www.acal.io). Heroku does not support naked domains out of the box. There are other solutions out there (they do cost money) but this works for now.

I then associated the domain with my app and opened it up in the browser.

1
2
heroku domains:add www.acal.io
heroku open

It worked!

Conclusion

That seems like a lot of work, but it actually didn’t take me that long to do. It’s a great learning project for those of you just starting out as a developer. Let me know if you can actually use this to make your own URL shortener, or if there is something I missed. Best of luck!

An Alernative to Figaro

I’ve made quite a few Rails apps that require storing keys from various proviers (Amazon, Facebok, Twitter, Stripe, etc). Thus far I’ve only either hard coded the values into my app (horribly insecure, espically since I use mainly public github repositories) or used the Figaro gem (which has caused bugs occasionally). Today, however I discovered a third way.

Step 1

Make a file called env_vars.rb that includes all your keys that you want to keep secret (below, I have put in my twitter keys and tokens) and place it in the app/config folder

1
2
3
4
ENV['TWITTER_CONSUMER_KEY'] =  'your_twitter_consumer_key'
ENV['TWITTER_CONSUMER_SECRET'] =  'your_twitter_consumer_secret'
ENV['TWITTER_OAUTH_TOKEN'] =  'your_twitter_oauth_token'
ENV['TWITTER_OAUTH_TOKEN_SECRET'] =  'your_twitter_oauth_token_secret'

Step 2

In app/config/boot.rb add the following line under require ‘rubygems’:

1
require_relative 'env_vars'

Step 3

Add the following line to the end of your .gitignore file

1
/config/env_vars.rb

Step 4

Now you can use whatever environmentl variables you stored in the env_var.rb file. Here is how I call the twitter variables in one of my app’s twitter.rb initializer.

1
2
3
4
5
6
Twitter.configure do |config|
  config.consumer_key = ENV["TWITTER_CONSUMER_KEY"]
  config.consumer_secret = ENV["TWITTER_CONSUMER_SECRET"]
  config.oauth_token = ENV["TWITTER_OAUTH_TOKEN"]
  config.oauth_token_secret = ENV["TWITTER_OAUTH_TOKEN_SECRET"]
end

Now, I am aware that there is an even cooler way to add these environmetal variables to your .bash_profile - but I’ll leave for another time. For now, I am satisfied that I can use environmental variables in a secure, Figaro-free way!

Climbing Mt. Stripe

This is (hopefully) part 1 of a muli-part series of blog posts about Stripe.

Incorporating a payment system into a web application has been an aspiration of mine for some time now. I kept putting it off because it seemed too daunting - the programming equivilant of climbing Mt. Everest. This weekend, however, a few flatiron students and I built an app called CodeWithUs. The idea is to allow people to post hackathons, and then get the right mix of people to attend them. We finished the data modeling and authentication on Saturday, and then took Sunday to fill in some of the views. Once the views started looking pretty good, Christina and I decided to integrate Stripe so that users could pay for a ticket to a hackathon.

We choose Stripe because we heard it was the easiest API for developers. This seemed like the perfect way to build our fist payment system! I first made a Stripe account - which took all of 2 minutes. Then we started looking at the excellent documentation and found a tutorial for Stripe Checkout using Ruby on Rails.

1
The Checkout makes it even easier to integrate Stripe directly into your website. We'll take care of building and styling credit card inputs, validation, error handling, and sending the encrypted card number securely to Stripe.

We followed the tutorial, but had to make some slight modifications sine we were incorporating this into an existing application.

We first added the Stripe gem to our Gemfile.

1
gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'

Then we generated a controller for charges.

1
$ rails g controller charges

We copied the suggested ‘new’ and ‘create’ actions from Stripe into our charges controller.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ChargesController < ApplicationController

def new
end

def create
  # Amount in cents
  @amount = 500

  customer = Stripe::Customer.create(
    :email => 'example@stripe.com',
    :card  => params[:stripeToken]
  )

  charge = Stripe::Charge.create(
    :customer    => customer.id,
    :amount      => @amount,
    :description => 'Rails Stripe customer',
    :currency    => 'usd'
  )

rescue Stripe::CardError => e
  flash[:error] = e.message
  redirect_to charges_path
end


end

Then we added a resource for charges to our routes file, which will give us all the standard routes we need.

1
resources :charges

At this point we created a new file called stripe.rb in the config/initializers folder. We were supposed to store our passwords safely, but Figaro, the Gem we were using to do this, was causing issues, so we temporarily hard coded the keys in.

1
2
3
4
5
6
Rails.configuration.stripe = {
  :publishable_key => 'OUR_STRIPE_PUBLISHABLE_KEY',
  :secret_key      => 'OUR_STRIPE_SECRET_KEY'
}

Stripe.api_key = Rails.configuration.stripe[:secret_key]

This is where we started to venture off the beaten path and go our own way. The tutorial wanted us to create html.erb files for the overall layout, and for the ‘new’ and ‘create’ actions. We already had a layout, and for the sake of getting this to work, we just appended the charges#new functionality into the hacakthon show page. We also were able to dynamically set the price in Stripe based on which hacakthon page the user was on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div><%= image_tag @hackathon.image %></div>

<ul>
    <li><%= @hackathon.name %></li>
    <li><%= @hackathon.start %></li>
    <li><%= @hackathon.end %></li>
    <li><%= @hackathon.location %></li>
    <li><%= @hackathon.description %></li>
    <li><%= @hackathon.capacity %></li>
</ul>

Participants signed-up:
<ul>
  <% @hackathon.users.each do |user| %>
    <li><%= user.name %></li>
  <% end %>
</ul>

<%= form_for(@participant) do |f| %>
  <%= f.hidden_field :hackathon_id, :value => params[:id] %>
  <%= f.submit "Join Hackathon!" %>
<% end %>


Pay Up!
<%= form_tag charges_path do %>
  <article>
    <label class="amount">
      <span>Amount: $<%= @hackathon.price %>.00</span>
    </label>
  </article>
  <script src="https://checkout.stripe.com/v2/checkout.js" class="stripe-button"
          data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
          data-description="<%= @hackathon.name %>"
          data-amount="<%= @hackathon.price*100 %>"></script>
<% end %>

At this point we were able to have our users click on ‘pay with card’, enter their credit card information, and actually buy a ticket to our hackathon!

This is how the page looks with the ‘Pay with Card’ button on the bottom.



This is what the popup looks like where a user can enter in their credit card information to buy a ticket.


The most important skill I’ve developed at the Flatiron School is getting better at taking big problems, and breaking them down into small ones. In order to climb Mt. Everest the first major goal is to make it to base camp one.

Similarily, the first step of fully integrating Stripe payments into your application is to allow a user to successfully buy one product from you. Today, we did this, and it feels great. However looking up, there is still a long way to go to the top of Mt. Stripe. We can’t actually accept real credit cards (only test ones), we are not capturing any information about our customers, and we can only do these transactions as a javascript popup. There is a lot of work left to go, but just accomplishing this small step has given me a lot of energy to push foward towards the summit!

Reopening the Fixnum Class

When doing Project Euler problem #14 I was running into a problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
The following iterative sequence is defined for the set of positive integers:

n  n/2 (n is even)
n  3n + 1 (n is odd)

Using the rule above and starting with 13, we generate the following sequence:

13  40  20  10  5  16  8  4  2  1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.

Which starting number, under one million, produces the longest chain?

NOTE: Once the chain starts the terms are allowed to go above one million.

I started out by writing a method that took a number and returned the length of its Collatz sequence.

1
2
3
4
5
6
7
8
9
10
11
12
def collatz
  array = []
  array << self
  until array.last == 1 do
    if array.last.odd?
      array << (array.last*3 + 1)
    else
      array << (array.last/2)
    end
  end
  array.count
end

I was pretty sure the logic was correct, but when I ran it on a random number it generated a strange error.

1
private method `collatz' called for 12:Fixnum (NoMethodError)

I tried a bunch of different approaches to solve this, but none of them were working. I eventually realized that my method needed to be part of a class. I made a new class called Collatz and made each number a new instance of that class. After half an hour of nothing but error messages I decided I needed a new approach.

Without an internet connection I couldn’t just google the problem, so I thought a bit more. It dawned on me that numbers are already objects (since everything in Ruby is an object) and that they are already part of the Fixnum class.

I’ve never modified a core Ruby class or ‘reopened’ a class before, but I did vaguely remember learning about this in a lecture a few weeks ago. So I decided to just reopen the Fixnum class and add this new method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Fixnum
  def collatz
    array = []
    array << self
    until array.last == 1 do
      if array.last.odd?
        array << (array.last*3 + 1)
      else
        array << (array.last/2)
      end
    end
    array.count
  end
end

I tired it out on a test number and it worked! Victory! This is pretty basic stuff, but it’s always satisfying to get past a problem you are stuck on it for a while. All that was left was to iterate over the first million numbers and determine which one had the longest Collatz sequence.

1
2
3
4
5
array = []
(1..1000000).each do |number|
  array << number.collatz
end
puts array.index(array.max) + 1

The program I built determined correctly that out of the first million numbers - 837,799 has the longest Collatz sequence. My code could be optimized further - it takes a good minute to run now. That would involved determining which numbers I don’t need to iterate over - for instance it seems that even numbers generally (but not always) have longer sequences as you increase, and that odd numbers generally have longer sequences than even numbers.

Saturday Morning Ruby

I’ve been immersed in Rails for a few weeks now. It’s been great, but it has come at the expense of pushing ahead and learning more about Ruby. I decided to go back and do another Project Euler problem this week, but it was a bit too easy. Today I took on a more challenging problem, and spent all morning getting it to work.

Project Euler Problem 11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
In the 2020 grid below, four numbers along a diagonal line have been marked in red.

08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48
The product of these numbers is 26  63  78  14 = 1788696.

What is the greatest product of four adjacent numbers in the same direction (up, down, left, right, or diagonally) in the 2020 grid?

1. Take grid and turn it into an array

I obviously needed to be able to access each value, and was pretty sure they had to be integers as I had plans to perform mathematical operations on them later on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
grid = "08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
        49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
        81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
        52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
        22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
        24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
        32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
        67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
        24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
        21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
        78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
        16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
        86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
        19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
        04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
        88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
        04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
        20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
        20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
        01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48"

grid = grid.split(' ').collect { |string| string.to_i }

2. Figure out mathematical plan

Rereading the instructions I determined there were four ways (horizontal, vertical, diagonal-right and diagonal-left) that combinations of numbers would be possible. Instead of trying to do this all at once and make one huge class with lots of sub methods, I decided that I would try each method one-by-one and if it produced the correct answer, I would stop.

3. Horizontal Combinations

This was the easiest, so I did it first.

First I tried to find the product of the first 4 numbers.

1
2
sum = grid[0..3].reduce{ |product, n| product * n }
puts sum

Once I was able to do this for the first 4 numbers, I tried to see if I could do this for any adjacent combiantion of 4 numbers in the first row

1
2
3
4
5
6
7
8
9
start = 0
finish = 3

until finish > 19 do
  sum = grid[start..finish].reduce{ |product, n| product * n }
  puts sum
  start += 1
  finish += 1
end

Then I figured out which product of all of these combinations was the greatest in value. As I went further down the rabbit hole I realized it would be helpful to encapsulate this logic into a method, which I did.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def find_largest_product(array)
  start = 0
  finish = 3
  greatest = 0

  until finish > 19 || finish > array.size do
  # I added the '|| finish > array.size' on the line above once I got to the diagonal lines and had to deal with arrays of varying size.
    sum = array[start..finish].reduce{ |product, n| product * n }
    greatest = sum if sum > greatest
    start += 1
    finish += 1
  end
  greatest
end

At this point I decided to split my array into nested arrays, with each nested array containing a row of 20 numbers.

1
2
3
4
until grid[0].is_a? Array
  row = grid.shift(20)
  grid << row
end

Once I had a nested array and a method to find the largest product, I then put the largest product of each row into a new array called answer, and then called the max method on that array to produce (what I hoped would be) the answer to the problem.

1
2
3
4
5
answer = []
  grid.each do |array|
    answer << find_largest_product(array)
  end
puts answer.max

Unfortunately, this was not the answer, and I had to move on and see if the solution would be a vertical combination.

4. Vertical Combinations

I now needed to make arrays of the columns from my origonal grid. I realized that I could just ‘shift’ off the first element of each of the row arrays I had previously made, and combine them into a new nested array which contained all the columns of data.

1
2
3
4
5
6
7
8
9
vertical = []
20.times do
  column = []
  grid.each do |row|
    item = row.shift
    column << item
  end
  vertical << column
end

Once I had this new array, I then see what the largest product of 4 adjacet vertical numbers were. I essentially reused the code I did when I did this for the rows of data, just replacing what array I was using. Looking back I could have put this into it’s own method too.

1
2
3
4
5
  answers = []
    vertical.each do |array|
      answers << find_largest_product(array)
    end
   puts answers.max

As with the horizontal combinations, this did not produce the correct answer. I knew that I was now venturing into more difficult territory with diagonals, but I was so invested in figuring out this problem that I was actually pretty excited.

5. Diagonal-Right Combinations

I tried to figure out what I needed to do in my head, but it wasn’t working. I went into excel and started visualizing it all, which was tremendously helpful.

I ended up determing that what I would do is make arrays of all the diagonals that went down and to the right. The green diagonals on the bottom-left and top-right represent the first and last array, since arrays beyond them would be less than 4 numbers in size. I figured if I started at the bottom-left most array and worked my way up to the top right most array, I would get all the diagonals in one fell swoop.

After some trial-and-error I was able to come up with a way to get all the numbers from a single diagonal line into an array, and then enapsulated it in a method called make_diagonal_array

1
2
3
4
5
6
7
8
9
def make_diagonal_array(horizontal_array, vertical, horizontal)
  diagonal_array = []
  until vertical > 19 || horizontal > 19 do
    diagonal_array << horizontal_array[vertical][horizontal]
    vertical += 1
    horizontal += 1
  end
  diagonal_array
end

Now that I could get a single diagonal array, I had to get all of them. I made a new array called diagonal, and shoveled each diagonal line onto it, again going from the bottom left most diagonal line to the top right most diagonal line. I used some of the same local varaible names that I used in the make_diagonal_array, which I call in the until loop. While this is a bit confusing, it was the least confusing way I could come up with at the time.

1
2
3
4
5
6
7
8
9
10
11
12
diagonal = []
vertical = 16
horizontal = 0

until vertical == 0 && horizontal > 16 do
  diagonal << make_diagonal_array(grid, vertical, horizontal)
  if vertical > 0
    vertical -= 1
  else
    horizontal += 1
  end
end

Hoping this would finally yield me the correct answer, I used the same code as I had done in the last two attempts.

1
2
3
4
5
answer = []
  diagonal.each do |array|
    answer << find_largest_product(array)
  end
puts answer.max

I suppose this was just my lucky day, since this did not produce the right answer. I then went on to try to final set of combiations: diagonal-left.

6. Diagonal-Left

For this step all I had to do was modify the code I made for diagonal-right. This time I would start at the top-left most diagonal and go down to the bottom-right most diagonal. This was suprisingly hard to wrap my head around, but I eventually did with enough fiddling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def make_diagonal_array(horizontal_array, vertical, horizontal)
  diagonal_array = []
  until vertical > 19 || horizontal < 0 do
    diagonal_array << horizontal_array[vertical][horizontal]
    vertical += 1
    horizontal -= 1
  end
  diagonal_array
end

diagonal = []
vertical = 0
horizontal = 3

until vertical > 16 && horizontal == 19 do
diagonal << make_diagonal_array(grid, vertical, horizontal)
  if horizontal <= 18
    horizontal += 1
  else
    vertical += 1
  end
end

answer = []
  diagonal.each do |array|
    answer << find_largest_product(array)
  end
puts answer.max

FINALLY!! I got the answer (it’s 70,600,674). This was quite a lot of work, but well worth it. If I were to do some serious refactoring, I could put this all into a class, further encapsulate these methods, and make it all work nicely as one program instead of 4 separate attempts. Perhaps another time though - I need to enjoy the rest of my Saturday!

Top 3 Reasons to Try Project Euler

A few days before I started the Flatiron School, Dave introduced me to Project Euler.

This is a website with hundreds of math problems that you need to solve by building a comptuer program. It is named after the 18th century Swiss mathemetician Leonhard Euler, who according to wikipedia is “one of the most prolific mathematicians ever”.

While there is some disagreement over how to pronounce Euler (some say ‘oil-er’, some say ‘you-ler’), there is a broad consensus that this is a great way to learn how to become a better programmer. However many people are intimidated by this website, since it involves math.

I’ve been going through these problems over the last few weeks and have a git repository of my work. I can say that yes it is a bit challenging, but that you learn so much that you should stop worrying, and just start doing the problems. I’ll take the problem I finished last night, and use it to show what I’ve been able to learn.

1
2
3
4
Summation of primes
Problem 10
The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
Find the sum of all the primes below two million.

1. You Get A Math Refresher

Instead of being intimidated by hard math concepts, just view it as an opportunity to hone your math skills! Math was always one of my favotite subjects in school because it made sense, and if I worked hard enough I could find a definitive answer. Perhaps I’m a bit unique in this, but doing math problems is really fun for me. For this problem, I had to jog my memory about prime numbers. I remembered that prime numbers were numbers greater than 1 that had no other factors besides 1 and itself. Not anything enlightening, but good to know I still remember middle school math concepts.

2. You Are Forced To Break Down Problems

I then realized I needed to break this problem down into many smaller problems. This has been one of the most critical skills I’ve developed while at the Flatiron School. If you are unable to take a big problem and break it down into smaller sub-problems, you won’t be able to do much of anything in programming. Project Euler has been one of the best resources I’ve found to practice doign this.

Here is how I broke down problem 10.

1
2
1. find all prime numbers under 2,000,000
2. take all these numbers and add them together

Once I started the first step I realized I could break it down further.

1
2
3
a. Find all the numbers under 2,000,000
b. Check if each one is prime
c. If it is prime, add it into an array

For the second step, I already knew that what I was doing (taking an array and adding it together) perfectly fit the pattern of reduce, and so didn’t need to further break it down. Avi should be proud that I’ve developed this kind of intuition.

3. You Learn How to Refactor

I ended up with some code that worked (the answer is 142,913,828,922), but was extremely slow

1
2
3
4
5
6
7
8
9
10
require 'prime'

array = []
(1...2000000).each do |num|
  if num.prime?
    array << num
  end
end

puts array.reduce{ |sum, n| sum + n }

At this point, I let myself go on the internet and try to find a way to refactor. I stumbled upon the ruby prime guide and realized that the prime module had more functionality that I could use to speed up my program. Here is my new code that I made after doing that research:

1
2
3
4
5
6
array = []
Prime.each(2000000) do |prime|
  array << prime
end

puts array.reduce{ |sum, n| sum + n }

It turns out that Prime is a class that is basically an array of every prime number. Since I am now only iterating over prime numbers as opposed to all numbers, my program went considerably faster - running in about half a minute as opposed to the several minutes it took to run the original.

Refactoring is a concept that some people get intimidated by, but by starting small and taking baby steps you can build up confidence to take on more challenging problems like refactoring huge controllers in rails.

So to all those on the fence about trying Project Euler - I say GO DO IT!

It’s Reduce, Not Inject

When I think of the term ‘inject’, I think of Ron Popeil’s Solid Flavor Injector - where you can literally put solid garlic cloves INTO a meat roast. Classy.

When I first came across Ruby’s method ‘Inject’ I was very confused. The point of inject is to get rid of local variables that iterate when you want to do something simple like sum up the first 10 numbers.

1
2
(1..10).inject(0) { |result, element| result + element }
#=>55

But what this ‘Inject’ method is really doing is reducing. Reducing a range of 10 numbers into just their one sum.┬áReducing the number of local variables you need to keep track of.┬áReducing the amount of code you need to write. This is why, even after I learned about inject, the name was still throwing me off. Then I discovered that ‘Reduce’ is actually an alias to ‘Inject’ in ruby! Thank you Matz for making me happy. I’ll be leaving the notion of injecting to Ron Popeil, and will write reduce methods in my code from now on.

How I Made Curb Your Flombaum

I’ve been at the flatiron school for about 3 weeks now. I’ve been learning so much - it’s really incredible how much progress I’ve been able to make in such a short period of time. This is in no small part due to the ability of Avi and Bob to both engage is in lectures and help us one-on-one when we get stuck. Part of being engaging in lectures though, has lead to some rather interesting things being said. Last semester the class started a blog to keep track of such utterances. I’m proud to say our semester has not only kept up this tradition, but in just three weeks already surpassed the last class in terms of the number of blog posts!

When I was asked to present to NYC on Rails, I knew that I was limited in my knowledge of programming, and had limited time to prepare since I was spending all day in class and all night studying and doing homework. I was supposed to find a topic, research it, and make a presentation. However I was determined to build something, no matter how small. On the first day of class, Avi ripped up his prepared lecture notes and said ‘we push code from day one’ and had us make a commit to github.Curb. I came here to build things, and so build something I did.

Curb Your Flombaum takes a random quote from the blog the class keeps about Avi, calls up my phone, and speaks it back to me. Here is how I did it.

1. Use feedzirra to parse XML and grab a random quote

There are lots of XML parsers out there, but I choose to use feedzirra because it seemed simple and had decent documentation. First, I had to require the Feedzira, Twilio, and Amazon S3 gems

1
2
3
require "feedzirra"
require 'twilio-ruby'
require 'aws/s3'

Then to actually get the quote I had to set the entire XML feed into a variable

1
feed = Feedzirra::Feed.fetch_and_parse('http://shitavisays.tumblr.com/rss')

From there I could just select a random entry be calling .sample and get just the title element (omitting any comments made beyond just Avi’s quote). The code will look like this

1
feed.entries.sample.title.to_s

2. Convert the random quote to an xml file

I now have the code that will give me back a string of a random Avi quote, but I need to put it in an actual XML file. To do that I’m first setting a variable xml to a string. This string is in the TwiML format, which I learned about using twillo’s excellent documentation. Twilio will automatically play an mp3 file if you wrap the url for the file in a command. Similarly Twilio will turn any text you wrap in a command into speech. Notice that I’ve put a \ before the quotes inside this string. I’m doing this to escape the characters so that ruby doesn’t prematurely end my string.

1
2
3
4
5
6
7
8
xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<Response>
  <Play>https://s3.amazonaws.com/NYConRails/intro.mp3</Play>
  <Pause length=\"0.5\"/>
  <Say voice=\"woman\">#{feed.entries.sample.title.to_s}</Say>
  <Pause length=\"0.5\"/>
  <Play>https://s3.amazonaws.com/NYConRails/exit.mp3</Play>
</Response>"

3. Save the file to my computer

Now that I have a dynamic variable for what an XML file would contain, I need to save it to my computer. Ruby’s official documentation made this task relatively easy.

1
2
3
File.open('call.xml', 'w+') { |f|
  f.puts xml
}

4. Upload the file to Amazon S3

Each time I run the program, I now have a new file called ‘call.xml’ that is saved to my computer. Each time it will include a new random Avi quote. I now need to host this file on the internet in order for Twilio to be able to work. I tried a lot of different solutions, but ultimately Amazon S3 was what I went with. I made an account, created a ‘bucket’ to store my files, and then was able to write more code. First I had to put in my credentials

1
2
3
4
AWS::S3::Base.establish_connection!(
  :access_key_id     => 'SDF7FS7DFS202S2LKSDS', # <-- put in your own, this is a fake one
  :secret_access_key => 'ASFDHSADF23HSDFASDFJSSX722iaf28#sdfsdf2' # <-- put in your own, this is a fake one
)

Then I had to actually upload the file to S3

1
2
file = 'call.xml'
  AWS::S3::S3Object.store(file, open(file), 'NYConRails', :access => :public_read)

Amazon’s official documentation left out the ‘AWS::S3’ prefix, which I had to figure out on my own. I also just made a guess on how to make the file public (it’s private by default), so they could improve that part of the documentation as well.

5. Use Twilio to convert the text into speech and call my phone

Now that I have an XML file with a random Avi quote hosted on the internet, I need to configure Twilio to do it’s magic. First I set my my credentials

1
2
account_sid = 'JH96987LKJLKJLKJsadfasdf3323423sfasd' # <-- put in your own, this is a fake one
auth_token = '98273234sdf2342309sd0f9s8fd980s3' # <-- put in your own, this is a fake one

Then I set up a client to talk to the Twilio REST API

1
@client = Twilio::REST::Client.new account_sid, auth_token

Finally I give twilio instructions on what I want it to do. I want it to call my phone (the to:) using the phone number i have associated with the account (the from:) using the instructions in TwiML format I have hosted on S3 (the url:). At first this did not work, but I asked Twilio for help and they got back to me saying I needed to specify that this was a GET request (the method:).

1
2
3
4
5
6
@call = @client.account.calls.create(
  :from => '+12129983322', # <-- put in your own (must be the one linked to your twilio account), this is a fake one
  :to => '+12123340076', # <-- put in whatever phone number you want to call, this is a fake one
  :url => 'https://s3.amazonaws.com/NYConRails/call.xml', # <-- put in your own, if you use S3 it will just be https://s3.amazonaws.com/YOUR_BUCKET_NAME/call.xml
  :method => 'GET'
)

In conclusion

So there is how I made Curb Your Flombaum. I have open sourced the project at github, and would love anyone to take this code and run with it. I have a lot of ideas for improving this app - maybe making the ability to sign up to get Avi quotes sent to your phone as they come in. However this was an MVP for a demo, and I’m proud to have done this after learning how to code for just three weeks.