From a1b7f11f7f734915ab61112eed2c985b3d18b05b Mon Sep 17 00:00:00 2001 From: Levi Klingler Date: Thu, 25 Jan 2018 18:14:14 -0500 Subject: [PATCH] Added simple shortener, counter, validations, and error handling --- source/Gemfile | 3 - source/Gemfile.lock | 3 - source/app/assets/javascripts/application.js | 1 - source/app/controllers/urls_controller.rb | 35 ++++++++++++ source/app/models/url.rb | 41 +++++++++++++ source/app/views/layouts/application.html.erb | 4 +- source/app/views/urls/_form.html.erb | 19 +++++++ source/app/views/urls/index.html.erb | 27 +++++++++ source/config/routes.rb | 57 +------------------ .../db/migrate/20180125164843_create_urls.rb | 10 ++++ .../20180125194335_add_click_count_to_urls.rb | 5 ++ source/db/schema.rb | 24 ++++++++ 12 files changed, 166 insertions(+), 63 deletions(-) create mode 100644 source/app/models/url.rb create mode 100644 source/app/views/urls/_form.html.erb create mode 100644 source/app/views/urls/index.html.erb create mode 100644 source/db/migrate/20180125164843_create_urls.rb create mode 100644 source/db/migrate/20180125194335_add_click_count_to_urls.rb create mode 100644 source/db/schema.rb diff --git a/source/Gemfile b/source/Gemfile index 9627b8b..6a4c4a5 100644 --- a/source/Gemfile +++ b/source/Gemfile @@ -16,8 +16,6 @@ gem 'coffee-rails', '~> 4.0.0' # Use jquery as the JavaScript library gem 'jquery-rails' -# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks -gem 'turbolinks' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. @@ -38,4 +36,3 @@ gem 'spring', group: :development # Use debugger # gem 'debugger', group: [:development, :test] gem 'rspec-rails', group: [:development, :test] - diff --git a/source/Gemfile.lock b/source/Gemfile.lock index fcf8b98..0eae19a 100644 --- a/source/Gemfile.lock +++ b/source/Gemfile.lock @@ -113,8 +113,6 @@ GEM thor (0.19.1) thread_safe (0.3.4) tilt (1.4.1) - turbolinks (2.4.0) - coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.5.3) @@ -134,5 +132,4 @@ DEPENDENCIES sdoc (~> 0.4.0) spring sqlite3 - turbolinks uglifier (>= 1.3.0) diff --git a/source/app/assets/javascripts/application.js b/source/app/assets/javascripts/application.js index d6925fa..1ca0e3b 100644 --- a/source/app/assets/javascripts/application.js +++ b/source/app/assets/javascripts/application.js @@ -12,5 +12,4 @@ // //= require jquery //= require jquery_ujs -//= require turbolinks //= require_tree . diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index ef26710..0d6ca07 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -1,2 +1,37 @@ class UrlsController < ApplicationController + + # GET /urls + def index + @urls = Url.all + @url = Url.new + end + + # POST /urls + def create + @url = Url.new(url_params) + + respond_to do |format| + if @url.save + format.html { redirect_to root_path, notice: 'Url was successfully created.' } + else + format.html { redirect_to root_path, notice: @url.errors.full_messages } + end + end + end + + # GET /:shortened_url + def redirect + @url = Url.where(shortened_url: params[:shortened_url]).take + if @url != nil + @url.update_attribute(:click_count, @url.click_count + 1) + redirect_to @url.original_url + else + redirect_to root_path, notice: 'We couldn\'t find a link for the bitly URL you clicked.' + end + end + + private + def url_params + params.require(:url).permit(:original_url, :shortened_url, :click_count) + end end diff --git a/source/app/models/url.rb b/source/app/models/url.rb new file mode 100644 index 0000000..a81ec9e --- /dev/null +++ b/source/app/models/url.rb @@ -0,0 +1,41 @@ +require 'uri' +require 'net/http' + +class Url < ActiveRecord::Base + ALPHANUMERIC_CHARACTERS = (('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a) + + validates_presence_of :original_url + validates_format_of :original_url, :with => URI::regexp, :message => "must be a valid Ruby URI" + validate :original_url_starts_with_http_or_https + validate :original_url_must_return_a_response + + before_save :shorten_url + + def original_url_starts_with_http_or_https + if not original_url =~ /^https?:\/\// + errors.add(:original_url, "must start with http:// or https://") + end + end + + def original_url_must_return_a_response + return unless original_url.present? && original_url =~ URI::regexp(['http', 'https']) + uri = URI.parse(original_url) + response = Net::HTTP.get_response(uri) + rescue URI::InvalidURIError + errors.add(:original_url, "must respond to a HTTP request") + rescue Errno::ECONNREFUSED + errors.add(:original_url, "must respond to a HTTP request") + rescue SocketError + errors.add(:original_url, "must respond to a HTTP request") + end + + def shorten_url + self.shortened_url = generate_key unless self.shortened_url + end + + def generate_key + key = "" + 7.times { key << ALPHANUMERIC_CHARACTERS.sample } + key + end +end diff --git a/source/app/views/layouts/application.html.erb b/source/app/views/layouts/application.html.erb index f946432..d5124e5 100644 --- a/source/app/views/layouts/application.html.erb +++ b/source/app/views/layouts/application.html.erb @@ -2,8 +2,8 @@ Source - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%= stylesheet_link_tag 'application', media: 'all' %> + <%= javascript_include_tag 'application' %> <%= csrf_meta_tags %> diff --git a/source/app/views/urls/_form.html.erb b/source/app/views/urls/_form.html.erb new file mode 100644 index 0000000..2324492 --- /dev/null +++ b/source/app/views/urls/_form.html.erb @@ -0,0 +1,19 @@ +<%= form_for(@url) do |f| %> + <% if @url.errors.any? %> +
+

<%= pluralize(@url.errors.count, "error") %> prohibited this url from being saved:

+ + +
+ <% end %> + +
+ <%= f.text_field :original_url %> + <%= f.hidden_field :click_count, value: 0 %> + <%= f.submit 'Shorten' %> +
+<% end %> diff --git a/source/app/views/urls/index.html.erb b/source/app/views/urls/index.html.erb new file mode 100644 index 0000000..8494a87 --- /dev/null +++ b/source/app/views/urls/index.html.erb @@ -0,0 +1,27 @@ +

Shorten a link

+ +<%= render 'form' %> + +

+ +

<%= notice %>

+ +

Your shortened links

+ + + + <% @urls.each do |url| %> + + + + + + + + + + + + <% end %> + +
<%= url.original_url %>
<%= link_to "http://localhost:3000/#{url.shortened_url}", "http://localhost:3000/#{url.shortened_url}" %>   Clicks: <%= url.click_count %>
 
diff --git a/source/config/routes.rb b/source/config/routes.rb index 3f66539..fe0566b 100644 --- a/source/config/routes.rb +++ b/source/config/routes.rb @@ -1,56 +1,5 @@ Rails.application.routes.draw do - # The priority is based upon order of creation: first created -> highest priority. - # See how all your routes lay out with "rake routes". - - # You can have the root of your site routed with "root" - # root 'welcome#index' - - # Example of regular route: - # get 'products/:id' => 'catalog#view' - - # Example of named route that can be invoked with purchase_url(id: product.id) - # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase - - # Example resource route (maps HTTP verbs to controller actions automatically): - # resources :products - - # Example resource route with options: - # resources :products do - # member do - # get 'short' - # post 'toggle' - # end - # - # collection do - # get 'sold' - # end - # end - - # Example resource route with sub-resources: - # resources :products do - # resources :comments, :sales - # resource :seller - # end - - # Example resource route with more complex sub-resources: - # resources :products do - # resources :comments - # resources :sales do - # get 'recent', on: :collection - # end - # end - - # Example resource route with concerns: - # concern :toggleable do - # post 'toggle' - # end - # resources :posts, concerns: :toggleable - # resources :photos, concerns: :toggleable - - # Example resource route within a namespace: - # namespace :admin do - # # Directs /admin/products/* to Admin::ProductsController - # # (app/controllers/admin/products_controller.rb) - # resources :products - # end + resources :urls, only: [:index, :create] + get '/:shortened_url', to: 'urls#redirect' + root 'urls#index' end diff --git a/source/db/migrate/20180125164843_create_urls.rb b/source/db/migrate/20180125164843_create_urls.rb new file mode 100644 index 0000000..a7067ea --- /dev/null +++ b/source/db/migrate/20180125164843_create_urls.rb @@ -0,0 +1,10 @@ +class CreateUrls < ActiveRecord::Migration + def change + create_table :urls do |t| + t.string :original_url + t.string :shortened_url + + t.timestamps + end + end +end diff --git a/source/db/migrate/20180125194335_add_click_count_to_urls.rb b/source/db/migrate/20180125194335_add_click_count_to_urls.rb new file mode 100644 index 0000000..8e0eda3 --- /dev/null +++ b/source/db/migrate/20180125194335_add_click_count_to_urls.rb @@ -0,0 +1,5 @@ +class AddClickCountToUrls < ActiveRecord::Migration + def change + add_column :urls, :click_count, :integer + end +end diff --git a/source/db/schema.rb b/source/db/schema.rb new file mode 100644 index 0000000..d95b2ed --- /dev/null +++ b/source/db/schema.rb @@ -0,0 +1,24 @@ +# 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: 20180125194335) do + + create_table "urls", force: true do |t| + t.string "original_url" + t.string "shortened_url" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "click_count" + end + +end