I built a nifty little Bitly clone in Rails to practice building an API backend. (The repo is here). It was similar to what my team built for Curator and it was nice to find reaffirmation in how straightforward it is to create this kind of web structure in Rails. Let’s jump into the code.

The program flow is straightfoward. There’s only three routes:

# config/routes

Rails.application.routes.draw do
  get '/shorten', to: "url#create",   as: "create_short_link"
  get '/data',    to: "url#index",    as: "get_click_data"
  get '/:short',  to: "url#show",     as: "redirect_link"
end

Let’s start with the first route. In order to use this API, the user needs to make a request with params. Here’s how my runner/test code looks for that:

# runner file

def shorten_link(url)
  uri = URI.parse("http://localhost:3000/shorten")
  params = {"url": url}
  uri.query = URI.encode_www_form(params)
  JSON.parse(Net::HTTP.get(uri))
end

So this sample link

https://coderwall.com/p/uh8kiw/pass-arrays-objects-via-querystring-the-rack-rails-way

looks like this when sent to my API:

http://localhost:3000/shorten?url=https%3A%2F%2Fcoderwall.com%2Fp%2Fuh8kiw%2Fpass-arrays-objects-via-querystring-the-rack-rails-way

When the request hits the /shorten route, it triggers the create action:

# UrlController

def create
  render json: shorten_multiple_links
end

The method shorten_multiple_links is found in the module URLActions. This method first looks to see whether the request params contain only one link, or an array of links:

# URLActions module

def shorten_multiple_links
  if params["urls"]
    json = {"urls": []}
    params["urls"].each{ |set| json[:urls] << shorten_single_link(set["url"]) }
    json
  else
    shorten_single_link(params["url"])
  end
end

This helps it construct a hash of multiple links or just a single link. Let’s look at what happens when passed that sample link. The method shorten_single_link gets called with the single link as the param:

# URLActions module

def shorten_single_link(link)
  url = Url.find_by(body: link)
  if url
    json = {"success": true, "url": link, "short": "#{ENV["host"]}#{url.short}"}
  else
    url = Url.new(body: link)

    if url.save
      json = {"success": true, "url": link, "short": "#{ENV["host"]}#{url.short}"}
    else
      json = {"success": false, "url": link, "errors": url.errors_as_string}
    end
  end

  json
end

It tries to find that link in my database. Let’s assume it’s not there–that means it needs to create a new entry, which it does by generating a new URL object. The model generates a short link using the callback before_create :generate_short, if: :no_short_exists?:

# URL model

private

def no_short_exists?
  self.short.nil?
end

def generate_short
  self.short = SecureRandom.hex(3)

  while Url.exists?(short: self.short)
    self.short = SecureRandom.hex(3)
  end
end

Then, assuming all went well and it was a legitimate initial link, the json value is returned back to the client. Here’s what that looks like:

{"success"=>true,
 "url"=>"https://coderwall.com/p/uh8kiw/pass-arrays-objects-via-querystring-the-rack-rails-way",
 "short"=>"http://localhost:3000/b2f6a8"}

Straightforward! Additionally, when using that short link, the controller simply routes the request that way:

# UrlController

def show
  short = params[:short]
  url = Url.find_by(short: short)

  if url
    url.click_count += 1
    url.save
    redirect_to url.body
  else
    @short = "#{ENV["host"]}#{short}"
    render "/not_found"
  end
end

Notice the click_count goes up by 1. We can query for the click_count for that link in a nice way:

{"success"=>true,
 "url"=>"https://coderwall.com/p/uh8kiw/pass-arrays-objects-via-querystring-the-rack-rails-waye",
 "click_count"=>11}

Meaning, that url’s short link has been used 11 times. Nifty!