Simple Short URLs

by Nikolai SharangovichMarch 6, 2013
With code samples, this blog post exemplifies how to easily make short URLs without employing third-party solutions.

Creating short URLs

I want to share with you an idea, or how to reinvent the wheel, if you need a short URL, but can’t use side solutions, such as goo.gl, and have a short domain. You can make a URL shorter on your own. First, we need to create a model with a destination path, parameter fields, and an appropriate controller. After that, we can route.

get '/:id' => 'short_url#show'

Then, we can make a redirect to a required path with saved parameters. If you need to create a very short URL for a tweet, for example, a digital :id can eat too many symbols in the result URL. To make it shorter, we can convert an :id number to a string (what, actually, URL shorters do). I have resolved this issue with the help of the Base64 standart. In our case, it’s just an array with characters and their IDs (codes). To convert an :id number to a short string, first, we convert it into a URL with Base64. This is the example of the ShortUrl model.

class ShortUrl < ActiveRecord::Base

  SYMBOLS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
  DEFAULT_URL = ["http://", APP_CONFIG['host'], (APP_CONFIG['port'].present? ? ":#{APP_CONFIG['port']}" : '')].join

  serialize :params

  def self.find_by_code string
    find decode(string)
  end

  def self.generate path, params
    short_url_record = create do |t|
      t.path = path
      t.params = params
      t.save
    end
    if short_url_record.id
      [DEFAULT_URL, '/', encode(short_url_record.id)].join
    else
      nil
    end
  end

  def full_path
    [DEFAULT_URL, path].join
  end

  private

  def self.encode number
    base = SYMBOLS.length
    res = []
    begin
      code = number % base
      number /= base
      res << SYMBOLS[code]
    end until number == 0
    res << 's'
    res.reverse.join
  end

  def self.decode string
    begin
      string = string.match(/\A[s](.{1,5})\z/)[1]
      i = string.length - 1
      res = 0
      string.each_char do |c|
        res += (SYMBOLS.index(c) * (SYMBOLS.length ** i))
        i -= 1
      end
      res
    rescue
      nil
    end
  end

end

If you want to leave both route variants (a digital ID and a string ID), you can put a prefix before an encoded string (in this case, it’s the s character) and use regular expression constraints in the route.

get '/:id' => 'short_url#show', constraints: { id: /[s].{1,5}/ }

The number of characters is limited to five, because you can encode a really big number (1073741823) with five characters that can never be used. These are some examples of encoding.

pry(main)> ShortUrl.encode 12345
=> "sDA5"
pry(main)> ShortUrl.encode 1000000
=> "sD0JA"

pry(main)> ShortUrl.encode 1056698302
=> "s-----"

pry(main)> ShortUrl.encode 1827200481836
=> "altoros"

Then, you should decode.

pry(main)> ShortUrl.decode "sFFFFF"
=> 85217605

So, you can generate short URLs.

ShortUrl.generate(edit_user_path(@screener), reply_to: reply_to)

The result should look like this.

=> "http://ru.by/sKa"

 

Further reading

 

About the author

Nikolai Sharangovich is an experienced Ruby on Rails developer with a deep understanding of the object-oriented design and modern software principles. He likes to collaborate with product people to achieve a maximum impact. Find him on GitHub.