
What's a URL shortener?
There are several services and some platforms providing URL shortener. Sometimes they call it link shortener, URL shrinker, Link compressor, vanity URL creator, but they all work the same:
- You enter a URL and the output is a short URL.
- You can use that short URL for example for social media sharing.
- When users click on the short link they are getting redirected to your origin URL.
- You can see stats about clicks on your links.
Some services provide a Custom URL shortener, so that you can register your domain and use that domain to shrink your URLs. Using your domain can create more trust for the ones who see the link.
The benefit of using such a service is that you will be able to track down clicks on links you are sharing on social media. The disadvantage is that you rely on third party services and if you can see the statistics of your clicks, the service will also do.
That's why we want to implement our Link shortener in Ruby On Rails.
The idea
The main model in our little app is a Link. We want to know these things from the link:
- URL -> The url someone entered
- Slug -> Our generated slug to create a short URL
- Clicked -> A counter for tracking clicks on the link
Adding database migration
Create a new database migration with needed attributes
# db/migrate/20191124150000_create_links.rb class CreateLinks < ActiveRecord::Migration def change create_table :links do |t| t.string :url t.string :slug t.integer :clicked, default: 0 t.timestamps null: false end end end
Create the model
Create a new model called link.rb:
# app/models/link.rb class Link < ActiveRecord::Base validates_presence_of :url validates :url, format: URI::regexp(%w[http https]) validates_uniqueness_of :slug validates_length_of :url, within: 3..255, on: :create, message: "too long" validates_length_of :slug, within: 3..255, on: :create, message: "too long" end
In our case we assume that no one has a link with more than 255 characters. But you never know, you can also use type "text" in your migration and change the validates_length_of to match a larger length.
You are now able to create links in your database with:
irb(main):001:0> link = Link.create(url: 'https://www.mylink.com/with-a-large-url', slug: 'shorty')
Adding a route
The idea is to create a single route for your app where you find the link in the DB for a given slug. Add this route to your routes.rb.
# routes.rb Rails.application.routes.draw do get '/s/:slug', to: 'links#show', as: :short end
Let's add a short method in our link class that we can get the route directly from the model:
# in app/models/links.rb def short Rails.application.routes.url_helpers.short_url(slug: self.slug) end
This method makes your life easier in the frontend. Just call .short and you get the short url for that link
irb(main):001:0> link = Link.create(url: "https://www.mylink.com/with-a-large-url", slug: "shorty") irb(main):002:0> link.short -> "https://www.mylink.com/s/shorty"
Create Link controller
The previously connected route points to a links_controller with the action show. Let's create the controller:
# app/controllers/link_controller.rb class LinksController < ApplicationController def show @link = Link.find_by_slug(params[:slug]) render 'errors/404', status: 404 if @link.nil? @link.update_attribute(:clicked, @link.clicked + 1) redirect_to @link.url end end
We assume you have an error page under views/errors/404, but you can also send an empty response with status 404 when a link was not found with the given slug. If a link for a slug was found, add +1 to the counter and redirect to the original URL.
Try it in your browser with https://www.mylink.com/s/shorty.
Automatic slug creation
If you don't care about the slug you can also generate it randomly. Add this before validation to your link model:
# in app/models/links.rb before_validation :generate_slug def generate_slug self.slug = SecureRandom.uuid[0..5] if self.slug.nil? || self.slug.empty? true end
So when you provide a slug, it tries to use that, otherwise you get a random string.
URL shortener API
To make the usage of your URL shortener easier we create a static method in the Link class to provide a small API.
# in app/models/links.rb def self.shorten(url, slug = '') # return short when URL with that slug was created before link = Link.where(url: url, slug: slug).first return link.short if link # create a new link = Link.new(url: url, slug: slug) return link.short if link.save # if slug is taken, try to add random characters Link.shorten(url, slug + SecureRandom.uuid[0..2]) end
This simple API allows you to do in you code stuff like:
irb(main):001:0> Link.shorten("https://www.mylink.com/with-a-large-url", "shorty") -> "https://www.mylink.com/s/shorty"
irb(main):001:0> Link.shorten("https://www.mylink.com/another-large-url") -> "https://www.mylink.com/s/4fg31"
I hope you are now able to handle your link shrinking technique in your ruby on rails app.
I ❤️Rails,
Simon
The full model
# app/models/link.rb class Link < ActiveRecord::Base validates_presence_of :url validates :url, format: URI::regexp(%w[http https]) validates_uniqueness_of :slug validates_length_of :url, within: 3..255, on: :create, message: "too long" validates_length_of :slug, within: 3..255, on: :create, message: "too long" # auto slug generation before_validation :generate_slug def generate_slug self.slug = SecureRandom.uuid[0..5] if self.slug.nil? || self.slug.empty? true end # fast access to the shortened link def short Rails.application.routes.url_helpers.short_url(slug: self.slug) end # the API def self.shorten(url, slug = '') link = Link.where(url: url, slug: slug).first return link.short if link link = Link.new(url: url, slug: slug) return link.short if link.save Link.shorten(url, slug + SecureRandom.uuid[0..2]) end end