
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