EvilZone

Programming and Scripting => Scripting Languages => : Fur October 06, 2014, 01:06:11 PM

: Ruby link shortener
: Fur October 06, 2014, 01:06:11 PM
Wrote this because I thought it would be cool. Wanted to make it as small and simple as possible.Took 4 hours because DataMapper was being weird and I don't do a lot of Ruby (or much programming in general these days ;_;).

Anyway, the code:

: (Ruby)
# linkshorten.rb
# Sinatra app to shorten urls.
# Date: 6th October 2014
# Author: Fur
 
# DataMapper stuff taken from: https://github.com/tomaszj/sinatra-datamapper-sample/blob/master/app.rb
 
# TODO:
  # Deduplicate argument validation (but where to put the functions?).

require 'rubygems'
require 'uri'
require 'sinatra'
require 'data_mapper'
require 'json'

DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/links.db")

class ShortenedUrl
  include DataMapper::Resource
  property :id, Serial
  property :url, String, :required => true
end
DataMapper.finalize.auto_upgrade!

class Base32
  # Base32 encode a number.
  def self.encode(number)
    number.to_s(32)
  end
 
  # Decode a base32-encoded string
  def self.decode(str)
    str.to_i(32)
  end

  # Is str encoded in base32?
  def self.is_encoded?(str)
    str =~ /[a-z0-9]+/
  end
end
 
class LinkShortener
 
  # Shortens a link and returns its code
  def self.shorten(url)
    if url !~ URI::regexp
      throw ArgumentError, "url must be a valid uri"
    end
    Base32.encode(ShortenedUrl.first_or_create(url: url).id)
  end
 
  # Gets the long url for a short url code
  def self.expand(code)
    if !Base32.is_encoded?(code)
      throw ArgumentError, "code must be a valid base32 string"
    end
    ShortenedUrl.get(Base32.decode(code)).url
  end

  # Gets the short url (like domain.tld/aaaa3) for a code and the route which will redirect short urls to their long ones.
  def self.code_to_short_url(code, base_url)
    (base_url.end_with?('/') ? base_url : base_url + '/') + code
  end

  def self.valid_code?(code)
    Base32.is_encoded?(code) && ShortenedUrl.all(:id => Base32.decode(code)).count > 0
  end
end
 
class Main < Sinatra::Base
  set :environment, :production
  set :show_exceptions, false

  post '/shorten' do
    if params[:url] !~ URI::regexp
      JSON.generate({:error => "url is not valid"})
    else
      code = LinkShortener.shorten params[:url]
      JSON.generate({:shortenedUrl => LinkShortener.code_to_short_url(code, request.base_url)})
    end
  end
 
  get '/:code' do
    if !LinkShortener.valid_code?(params[:code])
      "Invalid url"
    else
      redirect LinkShortener.expand(params[:code]), 302
    end
  end
end
Main.run!

Dependency installation (on Ubuntu at least):
: (Bash)
sudo gem install sinatra

sudo apt-get install libsqlite3-dev
sudo apt-get install ruby1.9.1-dev # Sometimes data_mapper fails to build native modules. This fixes it.

sudo gem install data_mapper

CHANGELOG:
  Reorganised code
  Removed code attribute in JSON output.
  Now uses dynamic JSON generator for readability reasons.
  Changed wording of exceptions a little
  Fixed an exception where trying to follow an invalid short url would cause an exception becaused by a forgotten else.
  Cleaned up the design a bit
  Redirect now redirectes with HTTP code 302.
  Cleaned TODO
: Re: Ruby link shortener
: lucid October 06, 2014, 10:57:42 PM
Nice to see someone else using Ruby ;D

I've never used sinatra or data_mapper but it looks useful.