News

Use Ruby and Sinatra to build a custom URL shortener

Make an impact with your brand and start sharing shortened URLs generated using your own application

sinatra800

As we make our daily dive into the world of social media, we see shortened URLs at every turn. Not only do they provide us with a quick and easy way to share potentially long URLs that are prone to being misspelled, they can also provide marketing benefits if the domain is brand-specific.

In this tutorial we will build our very own URL shortening service. The application itself will be built using Ruby and Sinatra – a small yet powerful addition to the programming language. Sinatra is a Domain Specific Language (DSL) that helps us create our application in a RESTful manner by defining certain HTTP actions, and then building the application to cater for these requests and how it will respond to them. Unlike other frameworks such as Rails, Sinatra has no concept of separate models, views or controllers. Nor does it have any helper functions to help you create forms, links or database connections. What it does offer you, however, is a simple way to create some truly powerful applications.

DOWNLOAD TUTORIAL FILES

Install Ruby

The installation procedure for Ruby is fairly straightforward. Windows users benefit from RailsInstaller (railsinstaller.org), which will install everything with ease thanks to the package. We recommend the Ruby Version Manager (RVM) application, available from rvm.io, which allows us to install and switch between multiple versions of the language.

Install Sinatra

To ensure we can run Sinatra applications, we need to install the required gems. Open the Terminal window and enter the following code to install the Sinatra gem. To assist us during the initial phases of building the application we’ll also need to install the gem for shotgun, which is a reloading development server:

001 > gem install sinatra
 002 > gem install shotgun

Sinatra template

Although Sinatra can run an application from a single file, larger projects may require specific directory structures. To help us get started, download the core template for the project from github.com/coldfumonkeh/URL-Shortener-Template or clone it using git in the command line. This template project has everything we need to build our application.

001 > git clone git://github.com/coldfumonkeh/URL-Shortener-
 Template.git urlshortener

First run

Once we’ve downloaded the template project files into your desired location, we’ll run the application. We first need to run the bundle command to install any required gems that our app needs. Once this has completed, we’ll run the application using the built-in WEBrick server on port 4567.

001 > cd urlshortener
 002 > bundle install
 003 > ruby application.rb

Missing route

Sinatra was kind enough to let us know that we had not added any routes for the application, and even gave us a simple example to fix the error. Let’s add that example route now, as well as a second route, to create another page within the application. Restart the WEBrick server to see the results.

001 get '/' do   
 002   "Hello World" 
 003 end
 004 
 005 get '/hello' do
 006   "Who shall we say hello to?"
 007 end

Using shotgun

As we saw in the previous step, we had to restart the server to pick up the updated code. This is where we can use shotgun instead, which reloads the code with every request to allow for continuous development. To run shotgun, simply run the application from the command line below, which will open on port 9393.

001 > shotgun application.rb

Sending parameters

Let’s create a third sample route within our application file, so we can see how we can pass variables into the URL, thereby creating more of a RESTful approach. This route will accept a parameter in the URL and display it directly in the browser. Browse to http://127.0.0.1:9393/Matt to see.

001 get '/hello/:name' do
 002   'Hello, ' + params[:name]
 003 end

Build model

Our URL shortening service application will use an SQLite3 database to store the saved URLs. To manage the control and validation of the values we will create a model to represent one entity from the database. Create ‘shorturl.rb’ in the lib folder and add the following code to complete the model.

001 class ShortURL
 002   include DataMapper::Resource
 003   property :short_url, String, key: true, unique_index: true, required: true
 004   property :url, Text, required: true
 005   property :created_at, DateTime
 006   property :updated_at, DateTime
 007 end

Root page

With our initial data model complete, let’s revise the routes for our application. Delete the example routes we added earlier, and add the new route for the home page in our application. This route checks for the existence of a URL parameter called url. If it exists, it will send it through to a new method to shrink it.

001 # root page
 002 get '/' do
 003 
 004   if params[:url] and not params[:url].empty?
 005     generate_short_url(params[:url])    
 006   else
 007     @urls = ShortURL.all;
 008     erb :index    
 009   end
 010 
 011 end     

Allow posts

We want users to be able to shorten a URL not only by sending a parameter via the URL and a GET request, but also by a form POST submission. Let’s add the revised route below the first. If no submission is made, we will display an index file to show the form and current number of URLs we have shortened.

001 post '/' do
 002 
 003   if params[:url] and not params[:url].empty?
 004     generate_short_url(params[:url])
 005   end
 006 
 007   @urls = ShortURL.all;
 008   erb :index
 009    
 010 end

Helper methods

To generate our shortened URL we will use helper methods. Add the following function to the helpers code block. This accepts the long URL provided by the user, generates the random string, saves the information to the database using the model and builds the new short URL, which we can use for display purposes later.

Random string

Our random string for the shortened URL is created by another helper method. Add this below the previous entry. This will generate a random string value limited to the total length of characters we send through, which in this application defaults to five.

001 def random_string(length) 
 002 rand(36**length).to_s(36) 
 003 end

Build URL

Finally, we’ll add our last helper method, which completes the functionality to create the short URL. This method will build the revised URL link, creating a string that concatenates the site URL from the configuration object and the generated random string. Add this below the other helper methods in application.rb.

001 def get_site_url(short_url)
 002 SiteConfig.url_base + short_url
 003 end

Track clicks

It may be useful to track or record how many times a shortened link, has been followed. Create a new model called clicktrack.rb in the lib directory and add the following code, which will validate the properties for the object and help to manage database transactions for us.

001 class ClickTrack
 002 include DataMapper::Resource
 003 property :id, Serial, key: true, unique_index: true, 
 required: true
 004 property :short_url, String, required: true
 005 property :url, Text, required: true
 006 property :clicked_at, DateTime 
 007 end

View URL

Create a new route in the application file to allow users to follow a generated link. This route allows for two entry points, both with the shortened URL in the path. If a matching short URL exists in the database, we’ll log the visit in the database using the clicktrack model before we redirect the user to the original long URL. The code for this step is on the coverdisc.

Expand info

Create a new route that will allow us to expand a shortened URL via an API request to return information about the link as a JSON object. In this route we set the content type for the response and set the values of the object from our model, which has retrieved the data from the database using a get() method.

Debug params

As we have the Terminal or command-line interface open during development, we can use this to help us debug and test our application. Add the following to the application file. This will output any parameters sent to our application in the Terminal window as they are submitted, which is very useful for testing expected parameters. The code for this step is on the coverdisc.

Create form

Open views/index.erb from the project files. We’ll add the form to allow users to submit URLs here. We can display the application title, already set in the configuration object, as well as the current count of generated URLs. The h() method escapes any unwanted HTML from the URL parameter.

001 < div class="container">
 002 
 003 < >h1>< %= SiteConfig.title %>< /h1>
 004 < p>Currently serving < %= @urls.count %> shortened 
 links< /p>
 005 < form method="post">
 006 < input type="text" value="< %= h(params[:url]) %/>" 
 name="url" id="url" />
 007 < input type="submit" value="shorten" id="submit" />
 008 < /form>
 009 < div class="clear">>
 010 

View results

Below the form in views/index.erb, let’s now add the code to display the freshly-created short URL after a successful request. Using a simple if statement to check for the existence of the variable, we can set the generated value as both the display text and the href attribute for the anchor tag.

001 <  % if @shortenedURL %>
 002 < div class="result">
 003 Your shortened URL is:
 004 < a href="< %= @shortenedURL %>">< %= @shortenedURL %>< /a>
 005 < /div>
 006 <  % end %>

Create test page

Create a new file called application_spec.rb in the spec directory. This will hold our unit tests for the application, which will help us debug and ensure our code works as expected. Add the following code into the test file to set up the dependencies and testing processes. Notice that we require the actual application file to run the code.
001 require_relative '../application.rb'
 002 require 'rack/test'
 003 
 004 set :environment, :test
 005 
 006 def app
 007 Sinatra::Application
 008 end
 009 
 010 describe 'URL Shortening Service' do
 011 
 012 include Rack::Test::Methods
 013 
 014 end

Define tests

Now we need to add some tests into the document. Place these tests after the Rack include in the tag block. The description of each test is clear, informative and written in plain English, which makes it ideal for anyone to read and understand what should be happening as it runs.

001 it "should load the home page" do
 002 get '/'
 003 last_response.should be_ok
 004 end
 005 
 006 it "should pass when a short url is viewed directly" do
 007 get '/jirey'
 008 last_response.should be_ok
 009 end
 010 
 011 it "should fail when trying to expand a hash that hasnt been sent" do
 012 get '/expand/'
 013 last_response.should_not be_ok
 014 end

Running tests

With our test cases written, we can now run the entire test script against our application to make sure everything is up and working as it should be. Open the Terminal window and type in the code below to run the rspec tests. The output and results will be visible in the Terminal.

001 > rspec spec/application_spec.rb   
×