Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wade branch #16

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions source/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ gem 'sdoc', '~> 0.4.0', group: :doc
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring', group: :development

# Bootstrap
gem 'bootstrap-sass', '3.2.0'

# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
gem 'bcrypt', '~> 3.1.7'

# Use unicorn as the app server
# gem 'unicorn'
Expand All @@ -37,5 +40,6 @@ gem 'spring', group: :development

# Use debugger
# gem 'debugger', group: [:development, :test]
gem 'rspec-rails', group: [:development, :test]
gem 'rspec-rails', group: [:development, :test]

gem 'autoprefixer-rails'
3 changes: 3 additions & 0 deletions source/app/assets/stylesheets/urls.css.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Place all the styles related to the Urls controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.error {
color: red;
}
42 changes: 42 additions & 0 deletions source/app/controllers/urls_controller.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,44 @@
class UrlsController < ApplicationController
def index
# for now, lets grab them all and pass them to a display page
@urls = Url.all
end

def new
# default behavior looks good
@target = ''
@save_errors = nil
end

def _extract_url_errors(url)
index = [:target_link, :linkid, :base].find { |i| url.errors[i].any? }
return nil if index.nil?
url.errors[index]
end

def create
# Actually create the new shortened url
@target = params[:target_link]
@errors = nil

@url = Url.new(target_link: @target)
if @url.save
redirect_to @url
else
@errors = _extract_url_errors(@url) ||
['Unable to save URL. Please try another.']
render 'new'
end
end

def show
@url = Url.find(params[:id])
end

# redirect to the long url
def follow
url = Url.find_by(linkid: params[:linkid])
Url.increment_counter(:click_count, url.id)
redirect_to url.target_link
end
end
6 changes: 6 additions & 0 deletions source/app/helpers/urls_helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
module UrlsHelper
module_function
LINKID_LENGTH = 8

def shortened_linkid(url)
Digest::SHA1.base64digest(url)[0, LINKID_LENGTH].tr('+/','-_')
end
end
43 changes: 43 additions & 0 deletions source/app/models/url.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'uri'
require 'net/http'

class Url < ActiveRecord::Base
before_save :ensure_short_link
validates :linkid, uniqueness: true
validate :target_link_not_empty, :target_link_must_be_http,
:target_link_must_be_reachable

def target_link_not_empty
return unless errors.empty?
return unless target_link.blank?
errors[:target_link] << 'Target URL cannot be blank'
end

def target_link_must_be_http
return unless errors.empty?
return if target_link.match(%r{\Ahttps?://})
errors[:target_link] << 'Target URL has an unrecognized scheme'
end

def _get_target_response_code
uri = URI.parse(target_link)
resp = Net::HTTP.get_response(uri)
resp.code.to_i
end

def target_link_must_be_reachable
return unless errors.empty?
return if _get_target_response_code < 400
errors[:target_link] << 'Target URL is not found'
rescue URI::InvalidURIError
errors[:target_link] << 'Target URL is not valid'
return
rescue
errors[:target_link] << 'Target URL is not reachable'
end

def ensure_short_link
return unless linkid.nil? || linkid.empty?
self.linkid = UrlsHelper.shortened_linkid(target_link)
end
end
2 changes: 1 addition & 1 deletion source/app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Source</title>
<title>URL Shortener</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
Expand Down
16 changes: 16 additions & 0 deletions source/app/views/urls/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<h1>URL Shortcuts</h1>

<a href="<%= new_url_path %>">Get new shortened URL</a>
<% if [email protected]? && @urls.size != 0 %>
<table>
<thead>
<tr><th>Shortened ID</th><th>Target</th></tr>
</thead>
<% @urls.each do |url| %>
<tr><td><a href="<%= url_url(url.id) %>"><%= url.linkid %></a></td>
<td><a href="<%= url.target_link %>"><%= url.target_link %></a></td></tr>
<% end %>
</table>
<% else %>
<p>No shortened URLs found</p>
<% end %>
13 changes: 13 additions & 0 deletions source/app/views/urls/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<h1>URL Shortener</h1>
<% if [email protected]? && @errors.size > 0 %>
<% @errors.each do |err| %>
<p class="error"><%= err %></p>
<% end %>
<% end %>
<form id="new_url" method="post" action="<%= urls_path %>">
<input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>"/>
<label for="#target_link">Target:</label>
<input type="text" name="target_link" id="target_link" size="100" placeholder="http://example.com/cool_page.html" value="<%= @target || '' %>">
<button class="btn btn-primary" type="submit">Shorten</button>
</form>
<a href="<%= urls_path %>">Back to list</a>
23 changes: 23 additions & 0 deletions source/app/views/urls/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<h1>URL Shortcut</h1>

<a href="<%= urls_path %>">Back to list</a>

<% if @url.nil? %>
<p>Unrecognized Shortcut.</p>
<% else %>
<table>
<tr>
<th>Shortened Link</th>
<td><a href="<%= follow_path(@url.linkid) %>"><%= follow_url(@url.linkid) %></a></td>
</tr>
<tr>
<th>Target Link</th>
<td><a href="<%= @url.target_link %>"><%= @url.target_link %></a></td>
</tr>
<tr>
<th>Click Count</th>
<td><%= @url.click_count %></td>
</tr>
</table>
<% end %>

3 changes: 3 additions & 0 deletions source/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

# Example resource route (maps HTTP verbs to controller actions automatically):
# resources :products
resources :urls, except: [ :edit, :update, :destroy ]

get '/:linkid', to: 'urls#follow', as: :follow, constraints: { linkid: /[-_a-zA-Z0-9]{8}/ }

# Example resource route with options:
# resources :products do
Expand Down
11 changes: 11 additions & 0 deletions source/db/migrate/20151012124222_create_urls.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateUrls < ActiveRecord::Migration
def change
create_table :urls do |t|
t.string :linkid, limit: 8
t.string :target_link, limit: 1024

t.timestamps
end
add_index :urls, :linkid, unique: true
end
end
5 changes: 5 additions & 0 deletions source/db/migrate/20151012132805_add_click_count_to_url.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddClickCountToUrl < ActiveRecord::Migration
def change
add_column :urls, :click_count, :integer, :default => 0
end
end
26 changes: 26 additions & 0 deletions source/db/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20151012132805) do

create_table "urls", force: true do |t|
t.string "linkid", limit: 8
t.string "target_link", limit: 1024
t.datetime "created_at"
t.datetime "updated_at"
t.integer "click_count", default: 0
end

add_index "urls", ["linkid"], name: "index_urls_on_linkid", unique: true

end
2 changes: 2 additions & 0 deletions source/db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
Url.create([{target_link: 'https://google.com'},
{target_link: 'http://guides.rubyonrails.org/v4.1.12/action_controller_overview.html'}])
99 changes: 99 additions & 0 deletions source/spec/controllers/urls_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
require 'rails_helper'

# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator. If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails. There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
#
# Compared to earlier versions of this generator, there is very limited use of
# stubs and message expectations in this spec. Stubs are only used when there
# is no simpler way to get a handle on the object needed for the example.
# Message expectations are only used when there is no simpler way to specify
# that an instance is receiving a specific message.

RSpec.describe UrlsController, type: :controller do
# This should return the minimal set of attributes required to create a valid
# Url. As you add validations to Url, be sure to
# adjust the attributes here as well.
let(:valid_attributes) { { target_link: 'http://google.com/' } }

let(:invalid_attributes) { { target_link: 'wqaesrdtfyjguhj' } }

# This should return the minimal set of values that should be in the session
# in order to pass any filters (e.g. authentication) defined in
# UrlsController. Be sure to keep this updated too.
let(:valid_session) { {} }

describe 'GET index' do
it 'assigns all urls as @urls' do
url = Url.create! valid_attributes
get :index, {}, valid_session
expect(assigns(:urls)).to eq([url])
end
end

describe 'GET show' do
it 'assigns the requested url as @url' do
url = Url.create! valid_attributes
get :show, { id: url.id }, valid_session
expect(assigns(:url)).to eq(url)
end
end

describe 'GET new' do
subject { get :new, {}, valid_session }

it 'renders the new Url form' do
expect(subject.status).to eq 200
expect(subject).to render_template :new
end
end

describe 'POST create' do
describe 'with valid params' do
it 'creates a new Url' do
expect { post :create, valid_attributes, valid_session }
.to change(Url, :count).by(1)
expect(response).to redirect_to(Url.last)
end
end

describe 'with invalid params' do
subject { post :create, invalid_attributes, valid_session }
it 're-renders the new template' do
expect(subject).to render_template :new
end
end

describe 'with missing target' do
subject { post :create, { target_link: '' }, valid_session }
it 're-renders the new template' do
expect(subject).to render_template :new
end
end

describe 'with unreachable link' do
bad_link = 'http://a.a.a.a.a.a.a/'
subject { post :create, { target_link: bad_link }, valid_session }
it 're-renders the new template' do
expect(subject).to render_template :new
end
end
end

describe 'GET follow' do
let(:url) { Url.create! valid_attributes }

it 'redirects to target' do
get :follow, { linkid: url.linkid }, valid_session
expect(response).to redirect_to url.target_link
end
end
end
18 changes: 18 additions & 0 deletions source/spec/helpers/urls_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require_relative '../../app/helpers/urls_helper'

describe 'UrlsHelper' do
let(:target) { 'http://covermymeds.com/' }
let(:linkid) { UrlsHelper.shortened_linkid(target) }

it 'returns a proper length linkid' do
expect(linkid.length).to eq UrlsHelper::LINKID_LENGTH
end

it 'linkid only contains legal characters' do
expect(linkid).to match(/^[-_a-zA-Z0-9]{8}$/)
end

it 'linkid length can\'t change with changing schema' do
expect(UrlsHelper::LINKID_LENGTH).to eq 8
end
end
Loading