Clone TinyURL (and friends) with Rails
tr.im is closing down, and the blogs are a-chatter about why URL-shortening services are probably a bad idea if you care about your links, and why you should roll your own service if you can.
Which is where Rails comes in.
It’s possible to clone TinyURL using Sinatra, but I’m more interested in Rails (I think Rails probably scales better). The basic setup is so simple, it doesn’t take long to write the code, and with Rails you can always add extras like an admin interface later without much fuss.
The code
(If you’re feeling lazy, the entire URL-shortening app is available on Github)
My TinyURL-like Rails app — I call it “Shrink” — has three main components: routes, controller, and model. Routes first:
[ruby]
ActionController::Routing::Routes.draw do |map|
map.resources :locations, :only => [:new, :create]
map.with_options :controller => ‘locations’ do |map|
map.root :action => ‘new’
map.preview ‘/:shrunken/preview’, :action => ‘preview’
map.location ‘/:shrunken’, :action => ‘redirect’
end
end
[/ruby]
“Locations” are the web pages we’ll be redirecting people to when they click on a short URL.
As you can probably tell, this routes file gives us RESTful routes for creating new locations, along with a catch-all shrunken route which we’ll use to intercept our short URLs and redirect them. There’s also a preview route (more on this in a bit), and I’ve mapped the root path (‘/’) to the new action on LocationsController — this will expose the form for shortening URLs on our site’s home page.
The Controller
We only need one controller, with just a few short methods:
[ruby]
class LocationsController < ApplicationController
def new
@location = Location.new
end
def preview
@location = Location.find_by_short_url(params[:shrunken])
end
def redirect
@location = Location.find_by_short_url(params[:shrunken])
redirect_to @location.url
end
def create
@location = Location.new(params[:location])
if @location.save
redirect_to preview_path(@location)
else
flash[:notice] = ‘Invalid URL’
render :action => "new"
end
end
end
[/ruby]
That’s simple enough — the only thing to note is that we’re using Location. find_by_short_url instead of Location.find, so we can use slugs like ‘a1b3c’ instead of database record IDs in our URLs.
The preview action, by the way, just renders a view which shows the user the shortened version of their URL so they can copy and paste it into Twitter (or whatever).
The Model
[ruby]
class Location < ActiveRecord::Base
validates_presence_of :url
before_create :validate_url
class << self
def find_by_short_url(shrunk)
find(shrunk.to_i(36))
end
end
def to_param
id.to_s(36)
end
private
def validate_url
false unless url_valid?(URI.parse(self.url))
end
def url_valid?(url)
url.kind_of?(URI::HTTP) || url.kind_of?(URI::HTTPS)
end
end
[/ruby]
The model implements our find_by_short_url method, implements some validations, and overrides Rails’ default to_param to provide TinyURL-like short URLs. to_param is used in url_for; the method normally returns an object’s database ID, for URLs like /people/1, but here we return a short string like ‘a1c’ instead.
Note the calls to to_s(36) and to_i(36): these convert record IDs into shorter URL slugs, and back again, using base 36 numbering. By using base 36, we can keep our URLs as short as possible: 10000 in base 10 is ‘7ps’ in base 36. Read more about this useful feature of Ruby at ruby-doc.org.
Putting it all together
The code for this app is open-source and is available on Github; download, run script/server, and go to http://0.0.0.0:3000/ and you’ll see it in action. Proof that you can clone TinyURL using Rails in less than an hour!
Comments Add your comments