From 77587f9fdcc346350f9077f1d8981de19787b278 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Tue, 20 Oct 2015 11:37:08 -0400 Subject: [PATCH 01/19] Initial version; url shortening and goto works --- source/.gitignore | 3 + .../app/assets/stylesheets/scaffolds.css.scss | 69 ++++++++ source/app/controllers/urls_controller.rb | 50 ++++++ source/app/models/url.rb | 8 + source/app/views/urls/_form.html.erb | 25 +++ source/app/views/urls/edit.html.erb | 6 + source/app/views/urls/index.html.erb | 25 +++ source/app/views/urls/index.json.jbuilder | 4 + source/app/views/urls/new.html.erb | 5 + source/app/views/urls/show.html.erb | 13 ++ source/app/views/urls/show.json.jbuilder | 1 + source/config/routes.rb | 3 + .../db/migrate/20151020143000_create_urls.rb | 10 ++ source/db/schema.rb | 23 +++ .../spec/controllers/urls_controller_spec.rb | 159 ++++++++++++++++++ source/spec/helpers/urls_helper_spec.rb | 15 ++ source/spec/models/url_spec.rb | 5 + source/spec/requests/urls_spec.rb | 10 ++ source/spec/routing/urls_routing_spec.rb | 35 ++++ source/spec/views/urls/edit.html.erb_spec.rb | 21 +++ source/spec/views/urls/index.html.erb_spec.rb | 22 +++ source/spec/views/urls/new.html.erb_spec.rb | 21 +++ source/spec/views/urls/show.html.erb_spec.rb | 16 ++ 23 files changed, 549 insertions(+) create mode 100644 source/app/assets/stylesheets/scaffolds.css.scss 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/edit.html.erb create mode 100644 source/app/views/urls/index.html.erb create mode 100644 source/app/views/urls/index.json.jbuilder create mode 100644 source/app/views/urls/new.html.erb create mode 100644 source/app/views/urls/show.html.erb create mode 100644 source/app/views/urls/show.json.jbuilder create mode 100644 source/db/migrate/20151020143000_create_urls.rb create mode 100644 source/db/schema.rb create mode 100644 source/spec/controllers/urls_controller_spec.rb create mode 100644 source/spec/helpers/urls_helper_spec.rb create mode 100644 source/spec/models/url_spec.rb create mode 100644 source/spec/requests/urls_spec.rb create mode 100644 source/spec/routing/urls_routing_spec.rb create mode 100644 source/spec/views/urls/edit.html.erb_spec.rb create mode 100644 source/spec/views/urls/index.html.erb_spec.rb create mode 100644 source/spec/views/urls/new.html.erb_spec.rb create mode 100644 source/spec/views/urls/show.html.erb_spec.rb diff --git a/source/.gitignore b/source/.gitignore index 6a502e9..8f75038 100644 --- a/source/.gitignore +++ b/source/.gitignore @@ -14,3 +14,6 @@ # Ignore all logfiles and tempfiles. /log/*.log /tmp + +# Ignore Vim swapfiles +.*.swp diff --git a/source/app/assets/stylesheets/scaffolds.css.scss b/source/app/assets/stylesheets/scaffolds.css.scss new file mode 100644 index 0000000..6ec6a8f --- /dev/null +++ b/source/app/assets/stylesheets/scaffolds.css.scss @@ -0,0 +1,69 @@ +body { + background-color: #fff; + color: #333; + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { + color: #000; + &:visited { + color: #666; + } + &:hover { + color: #fff; + background-color: #000; + } +} + +div { + &.field, &.actions { + margin-bottom: 10px; + } +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; + h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0px; + background-color: #c00; + color: #fff; + } + ul li { + font-size: 12px; + list-style: square; + } +} diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index ef26710..59720bf 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -1,2 +1,52 @@ class UrlsController < ApplicationController + before_action :set_url, only: [:show, :edit, :update, :destroy] + + # GET /urls + # GET /urls.json + def index + @urls = Url.all + end + + # GET /urls/1 + # GET /urls/1.json + def show + end + + # GET /urls/new + def new + @url = Url.new + end + + # POST /urls + # POST /urls.json + def create + @url = Url.new(url_params) + + respond_to do |format| + if @url.save + format.html { redirect_to @url, notice: 'Url was successfully created.' } + format.json { render :show, status: :created, location: @url } + else + format.html { render :new } + format.json { render json: @url.errors, status: :unprocessable_entity } + end + end + end + + # GET /goto/ + def goto + url = Url.find_by(short_url: params[:short_url]) + redirect_to url.real_url + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_url + @url = Url.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def url_params + params.require(:url).permit(:short_url, :real_url) + end end diff --git a/source/app/models/url.rb b/source/app/models/url.rb new file mode 100644 index 0000000..e3c17ea --- /dev/null +++ b/source/app/models/url.rb @@ -0,0 +1,8 @@ +class Url < ActiveRecord::Base + before_save :make_short_url + + private + def make_short_url + self.short_url = SecureRandom.hex(4) + end +end diff --git a/source/app/views/urls/_form.html.erb b/source/app/views/urls/_form.html.erb new file mode 100644 index 0000000..110b892 --- /dev/null +++ b/source/app/views/urls/_form.html.erb @@ -0,0 +1,25 @@ +<%= form_for(@url) do |f| %> + <% if @url.errors.any? %> +
+

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

+ +
    + <% @url.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= f.label :short_url %>
+ <%= f.text_field :short_url %> +
+
+ <%= f.label :real_url %>
+ <%= f.text_field :real_url %> +
+
+ <%= f.submit %> +
+<% end %> diff --git a/source/app/views/urls/edit.html.erb b/source/app/views/urls/edit.html.erb new file mode 100644 index 0000000..e52fdf4 --- /dev/null +++ b/source/app/views/urls/edit.html.erb @@ -0,0 +1,6 @@ +

Editing url

+ +<%= render 'form' %> + +<%= link_to 'Show', @url %> | +<%= link_to 'Back', urls_path %> diff --git a/source/app/views/urls/index.html.erb b/source/app/views/urls/index.html.erb new file mode 100644 index 0000000..50094b4 --- /dev/null +++ b/source/app/views/urls/index.html.erb @@ -0,0 +1,25 @@ +

Listing urls

+ + + + + + + + + + + + <% @urls.each do |url| %> + + + + + + <% end %> + +
Short urlReal url
<%= url.short_url %><%= url.real_url %><%= link_to 'Go to', "goto/#{url.short_url}" %>
+ +
+ +<%= link_to 'New Url', new_url_path %> diff --git a/source/app/views/urls/index.json.jbuilder b/source/app/views/urls/index.json.jbuilder new file mode 100644 index 0000000..6736639 --- /dev/null +++ b/source/app/views/urls/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@urls) do |url| + json.extract! url, :id, :short_url, :real_url + json.url url_url(url, format: :json) +end diff --git a/source/app/views/urls/new.html.erb b/source/app/views/urls/new.html.erb new file mode 100644 index 0000000..7e711d5 --- /dev/null +++ b/source/app/views/urls/new.html.erb @@ -0,0 +1,5 @@ +

New url

+ +<%= render 'form' %> + +<%= link_to 'Back', urls_path %> diff --git a/source/app/views/urls/show.html.erb b/source/app/views/urls/show.html.erb new file mode 100644 index 0000000..aa1e122 --- /dev/null +++ b/source/app/views/urls/show.html.erb @@ -0,0 +1,13 @@ +

<%= notice %>

+ +

+ Short url: + <%= @url.short_url %> +

+ +

+ Real url: + <%= @url.real_url %> +

+ +<%= link_to 'Back', urls_path %> diff --git a/source/app/views/urls/show.json.jbuilder b/source/app/views/urls/show.json.jbuilder new file mode 100644 index 0000000..5c060bf --- /dev/null +++ b/source/app/views/urls/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @url, :id, :short_url, :real_url, :created_at, :updated_at diff --git a/source/config/routes.rb b/source/config/routes.rb index 3f66539..0989921 100644 --- a/source/config/routes.rb +++ b/source/config/routes.rb @@ -1,4 +1,7 @@ Rails.application.routes.draw do + resources :urls, only: [:index, :new, :create, :show] + get 'goto/:short_url' => 'urls#goto' + # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/source/db/migrate/20151020143000_create_urls.rb b/source/db/migrate/20151020143000_create_urls.rb new file mode 100644 index 0000000..0e88fb5 --- /dev/null +++ b/source/db/migrate/20151020143000_create_urls.rb @@ -0,0 +1,10 @@ +class CreateUrls < ActiveRecord::Migration + def change + create_table :urls do |t| + t.string :short_url + t.string :real_url + + t.timestamps + end + end +end diff --git a/source/db/schema.rb b/source/db/schema.rb new file mode 100644 index 0000000..070a4c1 --- /dev/null +++ b/source/db/schema.rb @@ -0,0 +1,23 @@ +# 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: 20151020143000) do + + create_table "urls", force: true do |t| + t.string "short_url" + t.string "real_url" + t.datetime "created_at" + t.datetime "updated_at" + end + +end diff --git a/source/spec/controllers/urls_controller_spec.rb b/source/spec/controllers/urls_controller_spec.rb new file mode 100644 index 0000000..7014ddd --- /dev/null +++ b/source/spec/controllers/urls_controller_spec.rb @@ -0,0 +1,159 @@ +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) { + skip("Add a hash of attributes valid for your model") + } + + let(:invalid_attributes) { + skip("Add a hash of attributes invalid for your model") + } + + # 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.to_param}, valid_session + expect(assigns(:url)).to eq(url) + end + end + + describe "GET new" do + it "assigns a new url as @url" do + get :new, {}, valid_session + expect(assigns(:url)).to be_a_new(Url) + end + end + + describe "GET edit" do + it "assigns the requested url as @url" do + url = Url.create! valid_attributes + get :edit, {:id => url.to_param}, valid_session + expect(assigns(:url)).to eq(url) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new Url" do + expect { + post :create, {:url => valid_attributes}, valid_session + }.to change(Url, :count).by(1) + end + + it "assigns a newly created url as @url" do + post :create, {:url => valid_attributes}, valid_session + expect(assigns(:url)).to be_a(Url) + expect(assigns(:url)).to be_persisted + end + + it "redirects to the created url" do + post :create, {:url => valid_attributes}, valid_session + expect(response).to redirect_to(Url.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved url as @url" do + post :create, {:url => invalid_attributes}, valid_session + expect(assigns(:url)).to be_a_new(Url) + end + + it "re-renders the 'new' template" do + post :create, {:url => invalid_attributes}, valid_session + expect(response).to render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + let(:new_attributes) { + skip("Add a hash of attributes valid for your model") + } + + it "updates the requested url" do + url = Url.create! valid_attributes + put :update, {:id => url.to_param, :url => new_attributes}, valid_session + url.reload + skip("Add assertions for updated state") + end + + it "assigns the requested url as @url" do + url = Url.create! valid_attributes + put :update, {:id => url.to_param, :url => valid_attributes}, valid_session + expect(assigns(:url)).to eq(url) + end + + it "redirects to the url" do + url = Url.create! valid_attributes + put :update, {:id => url.to_param, :url => valid_attributes}, valid_session + expect(response).to redirect_to(url) + end + end + + describe "with invalid params" do + it "assigns the url as @url" do + url = Url.create! valid_attributes + put :update, {:id => url.to_param, :url => invalid_attributes}, valid_session + expect(assigns(:url)).to eq(url) + end + + it "re-renders the 'edit' template" do + url = Url.create! valid_attributes + put :update, {:id => url.to_param, :url => invalid_attributes}, valid_session + expect(response).to render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested url" do + url = Url.create! valid_attributes + expect { + delete :destroy, {:id => url.to_param}, valid_session + }.to change(Url, :count).by(-1) + end + + it "redirects to the urls list" do + url = Url.create! valid_attributes + delete :destroy, {:id => url.to_param}, valid_session + expect(response).to redirect_to(urls_url) + end + end + +end diff --git a/source/spec/helpers/urls_helper_spec.rb b/source/spec/helpers/urls_helper_spec.rb new file mode 100644 index 0000000..bbafaf3 --- /dev/null +++ b/source/spec/helpers/urls_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the UrlsHelper. For example: +# +# describe UrlsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe UrlsHelper, :type => :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/models/url_spec.rb b/source/spec/models/url_spec.rb new file mode 100644 index 0000000..209ca4c --- /dev/null +++ b/source/spec/models/url_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Url, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/requests/urls_spec.rb b/source/spec/requests/urls_spec.rb new file mode 100644 index 0000000..cd1976c --- /dev/null +++ b/source/spec/requests/urls_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe "Urls", :type => :request do + describe "GET /urls" do + it "works! (now write some real specs)" do + get urls_path + expect(response).to have_http_status(200) + end + end +end diff --git a/source/spec/routing/urls_routing_spec.rb b/source/spec/routing/urls_routing_spec.rb new file mode 100644 index 0000000..d4ec06f --- /dev/null +++ b/source/spec/routing/urls_routing_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +RSpec.describe UrlsController, :type => :routing do + describe "routing" do + + it "routes to #index" do + expect(:get => "/urls").to route_to("urls#index") + end + + it "routes to #new" do + expect(:get => "/urls/new").to route_to("urls#new") + end + + it "routes to #show" do + expect(:get => "/urls/1").to route_to("urls#show", :id => "1") + end + + it "routes to #edit" do + expect(:get => "/urls/1/edit").to route_to("urls#edit", :id => "1") + end + + it "routes to #create" do + expect(:post => "/urls").to route_to("urls#create") + end + + it "routes to #update" do + expect(:put => "/urls/1").to route_to("urls#update", :id => "1") + end + + it "routes to #destroy" do + expect(:delete => "/urls/1").to route_to("urls#destroy", :id => "1") + end + + end +end diff --git a/source/spec/views/urls/edit.html.erb_spec.rb b/source/spec/views/urls/edit.html.erb_spec.rb new file mode 100644 index 0000000..dca8bf1 --- /dev/null +++ b/source/spec/views/urls/edit.html.erb_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe "urls/edit", :type => :view do + before(:each) do + @url = assign(:url, Url.create!( + :short_url => "MyString", + :real_url => "MyString" + )) + end + + it "renders the edit url form" do + render + + assert_select "form[action=?][method=?]", url_path(@url), "post" do + + assert_select "input#url_short_url[name=?]", "url[short_url]" + + assert_select "input#url_real_url[name=?]", "url[real_url]" + end + end +end diff --git a/source/spec/views/urls/index.html.erb_spec.rb b/source/spec/views/urls/index.html.erb_spec.rb new file mode 100644 index 0000000..126001a --- /dev/null +++ b/source/spec/views/urls/index.html.erb_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe "urls/index", :type => :view do + before(:each) do + assign(:urls, [ + Url.create!( + :short_url => "Short Url", + :real_url => "Real Url" + ), + Url.create!( + :short_url => "Short Url", + :real_url => "Real Url" + ) + ]) + end + + it "renders a list of urls" do + render + assert_select "tr>td", :text => "Short Url".to_s, :count => 2 + assert_select "tr>td", :text => "Real Url".to_s, :count => 2 + end +end diff --git a/source/spec/views/urls/new.html.erb_spec.rb b/source/spec/views/urls/new.html.erb_spec.rb new file mode 100644 index 0000000..2119abf --- /dev/null +++ b/source/spec/views/urls/new.html.erb_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe "urls/new", :type => :view do + before(:each) do + assign(:url, Url.new( + :short_url => "MyString", + :real_url => "MyString" + )) + end + + it "renders new url form" do + render + + assert_select "form[action=?][method=?]", urls_path, "post" do + + assert_select "input#url_short_url[name=?]", "url[short_url]" + + assert_select "input#url_real_url[name=?]", "url[real_url]" + end + end +end diff --git a/source/spec/views/urls/show.html.erb_spec.rb b/source/spec/views/urls/show.html.erb_spec.rb new file mode 100644 index 0000000..7a84b64 --- /dev/null +++ b/source/spec/views/urls/show.html.erb_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe "urls/show", :type => :view do + before(:each) do + @url = assign(:url, Url.create!( + :short_url => "Short Url", + :real_url => "Real Url" + )) + end + + it "renders attributes in

" do + render + expect(rendered).to match(/Short Url/) + expect(rendered).to match(/Real Url/) + end +end From 50e64f42e44b9d9a9406ea807c451810d4754799 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Tue, 20 Oct 2015 11:47:28 -0400 Subject: [PATCH 02/19] Clean up navigation to be more RESTful --- source/app/controllers/urls_controller.rb | 11 +++-------- source/app/views/urls/index.html.erb | 2 +- source/config/routes.rb | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index 59720bf..88b7b5a 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -1,6 +1,4 @@ class UrlsController < ApplicationController - before_action :set_url, only: [:show, :edit, :update, :destroy] - # GET /urls # GET /urls.json def index @@ -10,6 +8,7 @@ def index # GET /urls/1 # GET /urls/1.json def show + redirect_to Url.find_by(short_url: params[:id]).real_url end # GET /urls/new @@ -24,7 +23,8 @@ def create respond_to do |format| if @url.save - format.html { redirect_to @url, notice: 'Url was successfully created.' } + @urls = Url.all + format.html { render :index, notice: 'Url was successfully created.' } format.json { render :show, status: :created, location: @url } else format.html { render :new } @@ -40,11 +40,6 @@ def goto end private - # Use callbacks to share common setup or constraints between actions. - def set_url - @url = Url.find(params[:id]) - end - # Never trust parameters from the scary internet, only allow the white list through. def url_params params.require(:url).permit(:short_url, :real_url) diff --git a/source/app/views/urls/index.html.erb b/source/app/views/urls/index.html.erb index 50094b4..c4d79ee 100644 --- a/source/app/views/urls/index.html.erb +++ b/source/app/views/urls/index.html.erb @@ -14,7 +14,7 @@ <%= url.short_url %> <%= url.real_url %> - <%= link_to 'Go to', "goto/#{url.short_url}" %> + <%= link_to 'Go to', url_path(url.short_url) %> <% end %> diff --git a/source/config/routes.rb b/source/config/routes.rb index 0989921..66e02d6 100644 --- a/source/config/routes.rb +++ b/source/config/routes.rb @@ -1,6 +1,5 @@ Rails.application.routes.draw do resources :urls, only: [:index, :new, :create, :show] - get 'goto/:short_url' => 'urls#goto' # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". From cb97f4a9de0c5c6069a5ab50776921aea38c7a4d Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Tue, 20 Oct 2015 12:48:18 -0400 Subject: [PATCH 03/19] Add click counter --- source/app/controllers/urls_controller.rb | 11 ++++------- source/app/models/url.rb | 2 +- source/app/views/urls/index.html.erb | 4 +++- .../migrate/20151020155111_add_click_count_to_urls.rb | 5 +++++ source/db/schema.rb | 3 ++- 5 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 source/db/migrate/20151020155111_add_click_count_to_urls.rb diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index 88b7b5a..f07e512 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -8,7 +8,10 @@ def index # GET /urls/1 # GET /urls/1.json def show - redirect_to Url.find_by(short_url: params[:id]).real_url + url = Url.find_by(short_url: params[:id]) + url.click_count += 1 + url.save + redirect_to url.real_url end # GET /urls/new @@ -33,12 +36,6 @@ def create end end - # GET /goto/ - def goto - url = Url.find_by(short_url: params[:short_url]) - redirect_to url.real_url - end - private # Never trust parameters from the scary internet, only allow the white list through. def url_params diff --git a/source/app/models/url.rb b/source/app/models/url.rb index e3c17ea..434c704 100644 --- a/source/app/models/url.rb +++ b/source/app/models/url.rb @@ -3,6 +3,6 @@ class Url < ActiveRecord::Base private def make_short_url - self.short_url = SecureRandom.hex(4) + self.short_url = SecureRandom.hex(4) if self.short_url.empty? end end diff --git a/source/app/views/urls/index.html.erb b/source/app/views/urls/index.html.erb index c4d79ee..2c021f9 100644 --- a/source/app/views/urls/index.html.erb +++ b/source/app/views/urls/index.html.erb @@ -5,6 +5,7 @@ Short url Real url + Clicks @@ -14,7 +15,8 @@ <%= url.short_url %> <%= url.real_url %> - <%= link_to 'Go to', url_path(url.short_url) %> + <%= url.click_count %> + <%= link_to 'Go to', "urls/#{url.short_url}" %> <% end %> diff --git a/source/db/migrate/20151020155111_add_click_count_to_urls.rb b/source/db/migrate/20151020155111_add_click_count_to_urls.rb new file mode 100644 index 0000000..93b0e1f --- /dev/null +++ b/source/db/migrate/20151020155111_add_click_count_to_urls.rb @@ -0,0 +1,5 @@ +class AddClickCountToUrls < ActiveRecord::Migration + def change + add_column :urls, :click_count, :integer, default: 0 + end +end diff --git a/source/db/schema.rb b/source/db/schema.rb index 070a4c1..b020d2d 100644 --- a/source/db/schema.rb +++ b/source/db/schema.rb @@ -11,13 +11,14 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151020143000) do +ActiveRecord::Schema.define(version: 20151020155111) do create_table "urls", force: true do |t| t.string "short_url" t.string "real_url" t.datetime "created_at" t.datetime "updated_at" + t.integer "click_count", default: 0 end end From b7fb95edc0c77fb6e765907d9b91d7d979ff651d Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Tue, 20 Oct 2015 12:50:05 -0400 Subject: [PATCH 04/19] Clean up goto link --- source/app/views/urls/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/views/urls/index.html.erb b/source/app/views/urls/index.html.erb index 2c021f9..996afa6 100644 --- a/source/app/views/urls/index.html.erb +++ b/source/app/views/urls/index.html.erb @@ -16,7 +16,7 @@ <%= url.short_url %> <%= url.real_url %> <%= url.click_count %> - <%= link_to 'Go to', "urls/#{url.short_url}" %> + <%= link_to 'Go to', url_path(url.short_url) %> <% end %> From 8121eaa83f436da6d6ea5420b1f6eeee2a3dc8b2 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Tue, 20 Oct 2015 13:46:45 -0400 Subject: [PATCH 05/19] Get rspec working --- source/.rspec | 2 + .../spec/controllers/urls_controller_spec.rb | 78 ++--------------- source/spec/models/url_spec.rb | 13 ++- source/spec/rails_helper.rb | 50 +++++++++++ source/spec/routing/urls_routing_spec.rb | 12 --- source/spec/spec_helper.rb | 85 +++++++++++++++++++ 6 files changed, 156 insertions(+), 84 deletions(-) create mode 100644 source/.rspec create mode 100644 source/spec/rails_helper.rb create mode 100644 source/spec/spec_helper.rb diff --git a/source/.rspec b/source/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/source/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/source/spec/controllers/urls_controller_spec.rb b/source/spec/controllers/urls_controller_spec.rb index 7014ddd..83ca400 100644 --- a/source/spec/controllers/urls_controller_spec.rb +++ b/source/spec/controllers/urls_controller_spec.rb @@ -24,11 +24,11 @@ # Url. As you add validations to Url, be sure to # adjust the attributes here as well. let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") + { short_url: 'c35c02c2', real_url: 'http://www.google.com', click_count: 2 } } let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") + { short_url: 'c35c02c2', real_url: 'httpq://www.google.com', click_count: -1 } } # This should return the minimal set of values that should be in the session @@ -45,10 +45,10 @@ end describe "GET show" do - it "assigns the requested url as @url" do + it "redirects to the real_url" do url = Url.create! valid_attributes - get :show, {:id => url.to_param}, valid_session - expect(assigns(:url)).to eq(url) + get :show, {:id => url.short_url}, valid_session + expect(response).to redirect_to(url.real_url) end end @@ -59,14 +59,6 @@ end end - describe "GET edit" do - it "assigns the requested url as @url" do - url = Url.create! valid_attributes - get :edit, {:id => url.to_param}, valid_session - expect(assigns(:url)).to eq(url) - end - end - describe "POST create" do describe "with valid params" do it "creates a new Url" do @@ -81,9 +73,9 @@ expect(assigns(:url)).to be_persisted end - it "redirects to the created url" do + it "redirects to the index url" do post :create, {:url => valid_attributes}, valid_session - expect(response).to redirect_to(Url.last) + expect(response).to render_template("index") end end @@ -100,60 +92,4 @@ end end - describe "PUT update" do - describe "with valid params" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - - it "updates the requested url" do - url = Url.create! valid_attributes - put :update, {:id => url.to_param, :url => new_attributes}, valid_session - url.reload - skip("Add assertions for updated state") - end - - it "assigns the requested url as @url" do - url = Url.create! valid_attributes - put :update, {:id => url.to_param, :url => valid_attributes}, valid_session - expect(assigns(:url)).to eq(url) - end - - it "redirects to the url" do - url = Url.create! valid_attributes - put :update, {:id => url.to_param, :url => valid_attributes}, valid_session - expect(response).to redirect_to(url) - end - end - - describe "with invalid params" do - it "assigns the url as @url" do - url = Url.create! valid_attributes - put :update, {:id => url.to_param, :url => invalid_attributes}, valid_session - expect(assigns(:url)).to eq(url) - end - - it "re-renders the 'edit' template" do - url = Url.create! valid_attributes - put :update, {:id => url.to_param, :url => invalid_attributes}, valid_session - expect(response).to render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested url" do - url = Url.create! valid_attributes - expect { - delete :destroy, {:id => url.to_param}, valid_session - }.to change(Url, :count).by(-1) - end - - it "redirects to the urls list" do - url = Url.create! valid_attributes - delete :destroy, {:id => url.to_param}, valid_session - expect(response).to redirect_to(urls_url) - end - end - end diff --git a/source/spec/models/url_spec.rb b/source/spec/models/url_spec.rb index 209ca4c..a326609 100644 --- a/source/spec/models/url_spec.rb +++ b/source/spec/models/url_spec.rb @@ -1,5 +1,16 @@ require 'rails_helper' RSpec.describe Url, :type => :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:valid_attributes) { + { short_url: 'c35c02c2', real_url: 'http://www.google.com', click_count: 2 } + } + + let(:valid_attributes_no_short_url) { + { short_url: '', real_url: 'http://www.google.com', click_count: 2 } + } + + it "assigns a short_url if none exists upon save" do + url = Url.create! valid_attributes_no_short_url + expect(url.short_url).not_to be_empty + end end diff --git a/source/spec/rails_helper.rb b/source/spec/rails_helper.rb new file mode 100644 index 0000000..e6c0b68 --- /dev/null +++ b/source/spec/rails_helper.rb @@ -0,0 +1,50 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV["RAILS_ENV"] ||= 'test' +require 'spec_helper' +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } + +# Checks for pending migrations before tests are run. +# If you are not using ActiveRecord, you can remove this line. +ActiveRecord::Migration.maintain_test_schema! + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! +end diff --git a/source/spec/routing/urls_routing_spec.rb b/source/spec/routing/urls_routing_spec.rb index d4ec06f..5d62e0f 100644 --- a/source/spec/routing/urls_routing_spec.rb +++ b/source/spec/routing/urls_routing_spec.rb @@ -15,21 +15,9 @@ expect(:get => "/urls/1").to route_to("urls#show", :id => "1") end - it "routes to #edit" do - expect(:get => "/urls/1/edit").to route_to("urls#edit", :id => "1") - end - it "routes to #create" do expect(:post => "/urls").to route_to("urls#create") end - it "routes to #update" do - expect(:put => "/urls/1").to route_to("urls#update", :id => "1") - end - - it "routes to #destroy" do - expect(:delete => "/urls/1").to route_to("urls#destroy", :id => "1") - end - end end diff --git a/source/spec/spec_helper.rb b/source/spec/spec_helper.rb new file mode 100644 index 0000000..275ba49 --- /dev/null +++ b/source/spec/spec_helper.rb @@ -0,0 +1,85 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause this +# file to always be loaded, without a need to explicitly require it in any files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need it. +# +# The `.rspec` file also contains a few flags that are not defaults but that +# users commonly want. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + # Limits the available syntax to the non-monkey patched syntax that is recommended. + # For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end From dbf14e33a1f0ee4314bee9edba8b27a66e4fa263 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 09:28:23 -0400 Subject: [PATCH 06/19] Add Url validation --- source/app/models/url.rb | 18 ++++++++++++++++++ source/spec/views/urls/edit.html.erb_spec.rb | 2 +- source/spec/views/urls/index.html.erb_spec.rb | 8 ++++---- source/spec/views/urls/show.html.erb_spec.rb | 4 ++-- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/source/app/models/url.rb b/source/app/models/url.rb index 434c704..3e039a0 100644 --- a/source/app/models/url.rb +++ b/source/app/models/url.rb @@ -1,8 +1,26 @@ +require 'uri' +require 'open-uri' + class Url < ActiveRecord::Base before_save :make_short_url + validates :real_url, presence: true + validate :test_url private def make_short_url self.short_url = SecureRandom.hex(4) if self.short_url.empty? end + + def test_url + # validate url is http or https + uri = URI(self.real_url) + errors.add(:real_url, 'is not http or https') unless uri.scheme == 'http' || uri.scheme == 'https' + + # validate url is accessible + begin + result = open(uri.to_s) + rescue + errors.add(:real_url, 'is not accessible') + end + end end diff --git a/source/spec/views/urls/edit.html.erb_spec.rb b/source/spec/views/urls/edit.html.erb_spec.rb index dca8bf1..9704a6d 100644 --- a/source/spec/views/urls/edit.html.erb_spec.rb +++ b/source/spec/views/urls/edit.html.erb_spec.rb @@ -4,7 +4,7 @@ before(:each) do @url = assign(:url, Url.create!( :short_url => "MyString", - :real_url => "MyString" + :real_url => "http://example.com" )) end diff --git a/source/spec/views/urls/index.html.erb_spec.rb b/source/spec/views/urls/index.html.erb_spec.rb index 126001a..a6f48c4 100644 --- a/source/spec/views/urls/index.html.erb_spec.rb +++ b/source/spec/views/urls/index.html.erb_spec.rb @@ -5,18 +5,18 @@ assign(:urls, [ Url.create!( :short_url => "Short Url", - :real_url => "Real Url" + :real_url => "http://example.com" ), Url.create!( :short_url => "Short Url", - :real_url => "Real Url" + :real_url => "https://google.com" ) ]) end it "renders a list of urls" do render - assert_select "tr>td", :text => "Short Url".to_s, :count => 2 - assert_select "tr>td", :text => "Real Url".to_s, :count => 2 + assert_select "tr>td", :text => "Short Url".to_s + assert_select "tr>td", :text => "http://example.com".to_s end end diff --git a/source/spec/views/urls/show.html.erb_spec.rb b/source/spec/views/urls/show.html.erb_spec.rb index 7a84b64..b83f902 100644 --- a/source/spec/views/urls/show.html.erb_spec.rb +++ b/source/spec/views/urls/show.html.erb_spec.rb @@ -4,13 +4,13 @@ before(:each) do @url = assign(:url, Url.create!( :short_url => "Short Url", - :real_url => "Real Url" + :real_url => "http://example.com" )) end it "renders attributes in

" do render expect(rendered).to match(/Short Url/) - expect(rendered).to match(/Real Url/) + expect(rendered).to match(/http:\/\/example.com/) end end From b8781f1fea3d3acc7ea21e0c0b0cdc338be16ef3 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 09:55:55 -0400 Subject: [PATCH 07/19] Remove short_url field from form --- source/app/models/url.rb | 2 +- source/app/views/urls/_form.html.erb | 4 ---- source/spec/views/urls/edit.html.erb_spec.rb | 4 ---- source/spec/views/urls/new.html.erb_spec.rb | 4 ---- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/source/app/models/url.rb b/source/app/models/url.rb index 3e039a0..108eb9f 100644 --- a/source/app/models/url.rb +++ b/source/app/models/url.rb @@ -8,7 +8,7 @@ class Url < ActiveRecord::Base private def make_short_url - self.short_url = SecureRandom.hex(4) if self.short_url.empty? + self.short_url = SecureRandom.hex(4) if self.short_url.nil? || self.short_url.empty? end def test_url diff --git a/source/app/views/urls/_form.html.erb b/source/app/views/urls/_form.html.erb index 110b892..d77bb79 100644 --- a/source/app/views/urls/_form.html.erb +++ b/source/app/views/urls/_form.html.erb @@ -11,10 +11,6 @@ <% end %> -

- <%= f.label :short_url %>
- <%= f.text_field :short_url %> -
<%= f.label :real_url %>
<%= f.text_field :real_url %> diff --git a/source/spec/views/urls/edit.html.erb_spec.rb b/source/spec/views/urls/edit.html.erb_spec.rb index 9704a6d..fb4b02b 100644 --- a/source/spec/views/urls/edit.html.erb_spec.rb +++ b/source/spec/views/urls/edit.html.erb_spec.rb @@ -3,7 +3,6 @@ RSpec.describe "urls/edit", :type => :view do before(:each) do @url = assign(:url, Url.create!( - :short_url => "MyString", :real_url => "http://example.com" )) end @@ -12,9 +11,6 @@ render assert_select "form[action=?][method=?]", url_path(@url), "post" do - - assert_select "input#url_short_url[name=?]", "url[short_url]" - assert_select "input#url_real_url[name=?]", "url[real_url]" end end diff --git a/source/spec/views/urls/new.html.erb_spec.rb b/source/spec/views/urls/new.html.erb_spec.rb index 2119abf..7096508 100644 --- a/source/spec/views/urls/new.html.erb_spec.rb +++ b/source/spec/views/urls/new.html.erb_spec.rb @@ -3,7 +3,6 @@ RSpec.describe "urls/new", :type => :view do before(:each) do assign(:url, Url.new( - :short_url => "MyString", :real_url => "MyString" )) end @@ -12,9 +11,6 @@ render assert_select "form[action=?][method=?]", urls_path, "post" do - - assert_select "input#url_short_url[name=?]", "url[short_url]" - assert_select "input#url_real_url[name=?]", "url[real_url]" end end From 757d2759974bd331d4e6484bced4eda707a830df Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 12:39:56 -0400 Subject: [PATCH 08/19] User auth now works, but I need to figure out authenticated rspec --- source/Gemfile | 2 +- source/Gemfile.lock | 5 + .../app/controllers/application_controller.rb | 1 + source/app/controllers/sessions_controller.rb | 18 ++ source/app/controllers/urls_controller.rb | 3 + source/app/controllers/users_controller.rb | 78 +++++++++ source/app/helpers/sessions_helper.rb | 16 ++ source/app/models/user.rb | 10 ++ source/app/views/layouts/application.html.erb | 7 + source/app/views/sessions/new.html.erb | 19 +++ source/app/views/users/_form.html.erb | 33 ++++ source/app/views/users/edit.html.erb | 6 + source/app/views/users/index.html.erb | 27 +++ source/app/views/users/index.json.jbuilder | 4 + source/app/views/users/new.html.erb | 5 + source/app/views/users/show.html.erb | 14 ++ source/app/views/users/show.json.jbuilder | 1 + source/config/routes.rb | 9 + .../db/migrate/20151021135657_create_users.rb | 10 ++ .../20151021140221_add_password_to_users.rb | 5 + source/db/schema.rb | 10 +- .../controllers/sessions_controller_spec.rb | 12 ++ .../spec/controllers/urls_controller_spec.rb | 19 ++- .../spec/controllers/users_controller_spec.rb | 159 ++++++++++++++++++ source/spec/helpers/sessions_helper_spec.rb | 15 ++ source/spec/helpers/users_helper_spec.rb | 15 ++ source/spec/models/user_spec.rb | 5 + source/spec/requests/users_spec.rb | 10 ++ source/spec/routing/users_routing_spec.rb | 35 ++++ .../spec/views/sessions/new.html.erb_spec.rb | 5 + source/spec/views/users/edit.html.erb_spec.rb | 22 +++ .../spec/views/users/index.html.erb_spec.rb | 25 +++ source/spec/views/users/new.html.erb_spec.rb | 22 +++ source/spec/views/users/show.html.erb_spec.rb | 17 ++ 34 files changed, 641 insertions(+), 3 deletions(-) create mode 100644 source/app/models/user.rb create mode 100644 source/app/views/sessions/new.html.erb create mode 100644 source/app/views/users/_form.html.erb create mode 100644 source/app/views/users/edit.html.erb create mode 100644 source/app/views/users/index.html.erb create mode 100644 source/app/views/users/index.json.jbuilder create mode 100644 source/app/views/users/new.html.erb create mode 100644 source/app/views/users/show.html.erb create mode 100644 source/app/views/users/show.json.jbuilder create mode 100644 source/db/migrate/20151021135657_create_users.rb create mode 100644 source/db/migrate/20151021140221_add_password_to_users.rb create mode 100644 source/spec/controllers/sessions_controller_spec.rb create mode 100644 source/spec/controllers/users_controller_spec.rb create mode 100644 source/spec/helpers/sessions_helper_spec.rb create mode 100644 source/spec/helpers/users_helper_spec.rb create mode 100644 source/spec/models/user_spec.rb create mode 100644 source/spec/requests/users_spec.rb create mode 100644 source/spec/routing/users_routing_spec.rb create mode 100644 source/spec/views/sessions/new.html.erb_spec.rb create mode 100644 source/spec/views/users/edit.html.erb_spec.rb create mode 100644 source/spec/views/users/index.html.erb_spec.rb create mode 100644 source/spec/views/users/new.html.erb_spec.rb create mode 100644 source/spec/views/users/show.html.erb_spec.rb diff --git a/source/Gemfile b/source/Gemfile index 9627b8b..07d617a 100644 --- a/source/Gemfile +++ b/source/Gemfile @@ -27,7 +27,7 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'spring', group: :development # Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '~> 3.1.7' # Use unicorn as the app server # gem 'unicorn' diff --git a/source/Gemfile.lock b/source/Gemfile.lock index fcf8b98..e7b2a20 100644 --- a/source/Gemfile.lock +++ b/source/Gemfile.lock @@ -28,6 +28,7 @@ GEM thread_safe (~> 0.1) tzinfo (~> 1.1) arel (5.0.1.20140414130214) + bcrypt (3.1.10) builder (3.2.2) coffee-rails (4.0.1) coffee-script (>= 2.2.0) @@ -125,6 +126,7 @@ PLATFORMS ruby DEPENDENCIES + bcrypt (~> 3.1.7) coffee-rails (~> 4.0.0) jbuilder (~> 2.0) jquery-rails @@ -136,3 +138,6 @@ DEPENDENCIES sqlite3 turbolinks uglifier (>= 1.3.0) + +BUNDLED WITH + 1.10.6 diff --git a/source/app/controllers/application_controller.rb b/source/app/controllers/application_controller.rb index d83690e..d8b3d09 100644 --- a/source/app/controllers/application_controller.rb +++ b/source/app/controllers/application_controller.rb @@ -2,4 +2,5 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + include SessionsHelper end diff --git a/source/app/controllers/sessions_controller.rb b/source/app/controllers/sessions_controller.rb index 16d11b5..a45daf1 100644 --- a/source/app/controllers/sessions_controller.rb +++ b/source/app/controllers/sessions_controller.rb @@ -1,2 +1,20 @@ class SessionsController < ApplicationController + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + log_in user + redirect_to root_url + else + flash[:danger] = 'Invalid email/password combination' + render 'new' + end + end + + def destroy + log_out + redirect_to login_path + end end diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index f07e512..f74ffa1 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -2,6 +2,7 @@ class UrlsController < ApplicationController # GET /urls # GET /urls.json def index + redirect_to login_path and return if !logged_in? @urls = Url.all end @@ -16,12 +17,14 @@ def show # GET /urls/new def new + redirect_to login_path and return if !logged_in? @url = Url.new end # POST /urls # POST /urls.json def create + redirect_to login_path and return if !logged_in? @url = Url.new(url_params) respond_to do |format| diff --git a/source/app/controllers/users_controller.rb b/source/app/controllers/users_controller.rb index 3e74dea..99781bf 100644 --- a/source/app/controllers/users_controller.rb +++ b/source/app/controllers/users_controller.rb @@ -1,2 +1,80 @@ class UsersController < ApplicationController + before_action :set_user, only: [:show, :edit, :update, :destroy] + + # GET /users + # GET /users.json + def index + redirect_to login_path and return if !logged_in? + @users = User.all + end + + # GET /users/1 + # GET /users/1.json + def show + redirect_to login_path and return if !logged_in? + end + + # GET /users/new + def new + @user = User.new + end + + # GET /users/1/edit + def edit + redirect_to login_path and return if !logged_in? + end + + # POST /users + # POST /users.json + def create + @user = User.new(user_params) + + respond_to do |format| + if @user.save + log_in @user + format.html { redirect_to @user, notice: 'User was successfully created.' } + format.json { render :show, status: :created, location: @user } + else + format.html { render :new } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /users/1 + # PATCH/PUT /users/1.json + def update + redirect_to login_path and return if !logged_in? + respond_to do |format| + if @user.update(user_params) + format.html { redirect_to @user, notice: 'User was successfully updated.' } + format.json { render :show, status: :ok, location: @user } + else + format.html { render :edit } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /users/1 + # DELETE /users/1.json + def destroy + redirect_to login_path and return if !logged_in? + @user.destroy + respond_to do |format| + format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_user + @user = User.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def user_params + params.require(:user).permit(:name, :email, :password, :password_confirmation) + end end diff --git a/source/app/helpers/sessions_helper.rb b/source/app/helpers/sessions_helper.rb index 309f8b2..ec23720 100644 --- a/source/app/helpers/sessions_helper.rb +++ b/source/app/helpers/sessions_helper.rb @@ -1,2 +1,18 @@ module SessionsHelper + def log_in(user) + session[:user_id] = user.id + end + + def log_out + session.delete(:user_id) + @current_user = nil + end + + def current_user + @current_user ||= User.find_by(id: session[:user_id]) + end + + def logged_in? + !current_user.nil? + end end diff --git a/source/app/models/user.rb b/source/app/models/user.rb new file mode 100644 index 0000000..51274de --- /dev/null +++ b/source/app/models/user.rb @@ -0,0 +1,10 @@ +class User < ActiveRecord::Base + before_save { self.email = email.downcase } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: { case_sensitive: false } + has_secure_password + validates :password, presence: true, length: { minimum: 6 } +end diff --git a/source/app/views/layouts/application.html.erb b/source/app/views/layouts/application.html.erb index f946432..7c7c6d3 100644 --- a/source/app/views/layouts/application.html.erb +++ b/source/app/views/layouts/application.html.erb @@ -7,6 +7,13 @@ <%= csrf_meta_tags %> + <%= yield %> diff --git a/source/app/views/sessions/new.html.erb b/source/app/views/sessions/new.html.erb new file mode 100644 index 0000000..fbcca34 --- /dev/null +++ b/source/app/views/sessions/new.html.erb @@ -0,0 +1,19 @@ +<% provide(:title, "Log in") %> +

Log in

+ +
+
+ <%= form_for(:session, url: login_path) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

New user? <%= link_to "Sign up now!", signup_path %>

+
+
diff --git a/source/app/views/users/_form.html.erb b/source/app/views/users/_form.html.erb new file mode 100644 index 0000000..efe5fcc --- /dev/null +++ b/source/app/views/users/_form.html.erb @@ -0,0 +1,33 @@ +<%= form_for(@user) do |f| %> + <% if @user.errors.any? %> +
+

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

+ +
    + <% @user.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= f.label :name %>
+ <%= f.text_field :name %> +
+
+ <%= f.label :email %>
+ <%= f.text_field :email %> +
+
+ <%= f.label :password %>
+ <%= f.password_field :password %> +
+
+ <%= f.label :password_confirmation, "Confirmation" %>
+ <%= f.password_field :password_confirmation %> +
+
+ <%= f.submit %> +
+<% end %> diff --git a/source/app/views/users/edit.html.erb b/source/app/views/users/edit.html.erb new file mode 100644 index 0000000..99bd4cc --- /dev/null +++ b/source/app/views/users/edit.html.erb @@ -0,0 +1,6 @@ +

Editing user

+ +<%= render 'form' %> + +<%= link_to 'Show', @user %> | +<%= link_to 'Back', users_path %> diff --git a/source/app/views/users/index.html.erb b/source/app/views/users/index.html.erb new file mode 100644 index 0000000..6041d5e --- /dev/null +++ b/source/app/views/users/index.html.erb @@ -0,0 +1,27 @@ +

Listing users

+ + + + + + + + + + + + <% @users.each do |user| %> + + + + + + + + <% end %> + +
NameEmail
<%= user.name %><%= user.email %><%= link_to 'Show', user %><%= link_to 'Edit', edit_user_path(user) %><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New User', new_user_path %> diff --git a/source/app/views/users/index.json.jbuilder b/source/app/views/users/index.json.jbuilder new file mode 100644 index 0000000..2972532 --- /dev/null +++ b/source/app/views/users/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@users) do |user| + json.extract! user, :id, :name, :email + json.url user_url(user, format: :json) +end diff --git a/source/app/views/users/new.html.erb b/source/app/views/users/new.html.erb new file mode 100644 index 0000000..efc0404 --- /dev/null +++ b/source/app/views/users/new.html.erb @@ -0,0 +1,5 @@ +

New user

+ +<%= render 'form' %> + +<%= link_to 'Back', users_path %> diff --git a/source/app/views/users/show.html.erb b/source/app/views/users/show.html.erb new file mode 100644 index 0000000..3816c64 --- /dev/null +++ b/source/app/views/users/show.html.erb @@ -0,0 +1,14 @@ +

<%= notice %>

+ +

+ Name: + <%= @user.name %> +

+ +

+ Email: + <%= @user.email %> +

+ +<%= link_to 'Edit', edit_user_path(@user) %> | +<%= link_to 'Back', users_path %> diff --git a/source/app/views/users/show.json.jbuilder b/source/app/views/users/show.json.jbuilder new file mode 100644 index 0000000..53f1bb9 --- /dev/null +++ b/source/app/views/users/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @user, :id, :name, :email, :created_at, :updated_at diff --git a/source/config/routes.rb b/source/config/routes.rb index 66e02d6..e6edb40 100644 --- a/source/config/routes.rb +++ b/source/config/routes.rb @@ -1,6 +1,15 @@ Rails.application.routes.draw do + get 'login' => 'sessions#new' + post 'login' => 'sessions#create' + delete 'logout' => 'sessions#destroy' + get 'signup' => 'users#new' + + resources :users + resources :urls, only: [:index, :new, :create, :show] + root 'urls#index' + # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/source/db/migrate/20151021135657_create_users.rb b/source/db/migrate/20151021135657_create_users.rb new file mode 100644 index 0000000..7e0dcd9 --- /dev/null +++ b/source/db/migrate/20151021135657_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/source/db/migrate/20151021140221_add_password_to_users.rb b/source/db/migrate/20151021140221_add_password_to_users.rb new file mode 100644 index 0000000..7b370a5 --- /dev/null +++ b/source/db/migrate/20151021140221_add_password_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordToUsers < ActiveRecord::Migration + def change + add_column :users, :password_digest, :string + end +end diff --git a/source/db/schema.rb b/source/db/schema.rb index b020d2d..431986d 100644 --- a/source/db/schema.rb +++ b/source/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151020155111) do +ActiveRecord::Schema.define(version: 20151021140221) do create_table "urls", force: true do |t| t.string "short_url" @@ -21,4 +21,12 @@ t.integer "click_count", default: 0 end + create_table "users", force: true do |t| + t.string "name" + t.string "email" + t.datetime "created_at" + t.datetime "updated_at" + t.string "password_digest" + end + end diff --git a/source/spec/controllers/sessions_controller_spec.rb b/source/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 0000000..be9f566 --- /dev/null +++ b/source/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe SessionsController, :type => :controller do + + describe "GET new" do + it "returns http success" do + get :new + expect(response).to have_http_status(:success) + end + end + +end diff --git a/source/spec/controllers/urls_controller_spec.rb b/source/spec/controllers/urls_controller_spec.rb index 83ca400..db5967d 100644 --- a/source/spec/controllers/urls_controller_spec.rb +++ b/source/spec/controllers/urls_controller_spec.rb @@ -34,12 +34,19 @@ # 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) { {} } + let(:valid_session) { { user_id: 2 } } + include SessionsHelper describe "GET index" do + it "redirects to login page if not logged in" do + get :index + expect(response).to redirect_to(login_path) + end + it "assigns all urls as @urls" do url = Url.create! valid_attributes get :index, {}, valid_session + print "here: #{User.find_by(id: session[:user_id])}" expect(assigns(:urls)).to eq([url]) end end @@ -53,6 +60,11 @@ end describe "GET new" do + it "redirects to login page if not logged in" do + get :new + expect(response).to redirect_to(login_path) + end + it "assigns a new url as @url" do get :new, {}, valid_session expect(assigns(:url)).to be_a_new(Url) @@ -60,6 +72,11 @@ end describe "POST create" do + it "redirects to login page if not logged in" do + post :create + expect(response).to redirect_to(login_path) + end + describe "with valid params" do it "creates a new Url" do expect { diff --git a/source/spec/controllers/users_controller_spec.rb b/source/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000..8139f65 --- /dev/null +++ b/source/spec/controllers/users_controller_spec.rb @@ -0,0 +1,159 @@ +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 UsersController, :type => :controller do + + # This should return the minimal set of attributes required to create a valid + # User. As you add validations to User, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + skip("Add a hash of attributes valid for your model") + } + + let(:invalid_attributes) { + skip("Add a hash of attributes invalid for your model") + } + + # 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 + # UsersController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe "GET index" do + it "assigns all users as @users" do + user = User.create! valid_attributes + get :index, {}, valid_session + expect(assigns(:users)).to eq([user]) + end + end + + describe "GET show" do + it "assigns the requested user as @user" do + user = User.create! valid_attributes + get :show, {:id => user.to_param}, valid_session + expect(assigns(:user)).to eq(user) + end + end + + describe "GET new" do + it "assigns a new user as @user" do + get :new, {}, valid_session + expect(assigns(:user)).to be_a_new(User) + end + end + + describe "GET edit" do + it "assigns the requested user as @user" do + user = User.create! valid_attributes + get :edit, {:id => user.to_param}, valid_session + expect(assigns(:user)).to eq(user) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new User" do + expect { + post :create, {:user => valid_attributes}, valid_session + }.to change(User, :count).by(1) + end + + it "assigns a newly created user as @user" do + post :create, {:user => valid_attributes}, valid_session + expect(assigns(:user)).to be_a(User) + expect(assigns(:user)).to be_persisted + end + + it "redirects to the created user" do + post :create, {:user => valid_attributes}, valid_session + expect(response).to redirect_to(User.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved user as @user" do + post :create, {:user => invalid_attributes}, valid_session + expect(assigns(:user)).to be_a_new(User) + end + + it "re-renders the 'new' template" do + post :create, {:user => invalid_attributes}, valid_session + expect(response).to render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + let(:new_attributes) { + skip("Add a hash of attributes valid for your model") + } + + it "updates the requested user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => new_attributes}, valid_session + user.reload + skip("Add assertions for updated state") + end + + it "assigns the requested user as @user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => valid_attributes}, valid_session + expect(assigns(:user)).to eq(user) + end + + it "redirects to the user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => valid_attributes}, valid_session + expect(response).to redirect_to(user) + end + end + + describe "with invalid params" do + it "assigns the user as @user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => invalid_attributes}, valid_session + expect(assigns(:user)).to eq(user) + end + + it "re-renders the 'edit' template" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => invalid_attributes}, valid_session + expect(response).to render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested user" do + user = User.create! valid_attributes + expect { + delete :destroy, {:id => user.to_param}, valid_session + }.to change(User, :count).by(-1) + end + + it "redirects to the users list" do + user = User.create! valid_attributes + delete :destroy, {:id => user.to_param}, valid_session + expect(response).to redirect_to(users_url) + end + end + +end diff --git a/source/spec/helpers/sessions_helper_spec.rb b/source/spec/helpers/sessions_helper_spec.rb new file mode 100644 index 0000000..48129e2 --- /dev/null +++ b/source/spec/helpers/sessions_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the SessionsHelper. For example: +# +# describe SessionsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe SessionsHelper, :type => :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/helpers/users_helper_spec.rb b/source/spec/helpers/users_helper_spec.rb new file mode 100644 index 0000000..0971a2f --- /dev/null +++ b/source/spec/helpers/users_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the UsersHelper. For example: +# +# describe UsersHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe UsersHelper, :type => :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/models/user_spec.rb b/source/spec/models/user_spec.rb new file mode 100644 index 0000000..0bc0e60 --- /dev/null +++ b/source/spec/models/user_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe User, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/requests/users_spec.rb b/source/spec/requests/users_spec.rb new file mode 100644 index 0000000..c05be49 --- /dev/null +++ b/source/spec/requests/users_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe "Users", :type => :request do + describe "GET /users" do + it "works! (now write some real specs)" do + get users_path + expect(response).to have_http_status(200) + end + end +end diff --git a/source/spec/routing/users_routing_spec.rb b/source/spec/routing/users_routing_spec.rb new file mode 100644 index 0000000..751ad75 --- /dev/null +++ b/source/spec/routing/users_routing_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +RSpec.describe UsersController, :type => :routing do + describe "routing" do + + it "routes to #index" do + expect(:get => "/users").to route_to("users#index") + end + + it "routes to #new" do + expect(:get => "/users/new").to route_to("users#new") + end + + it "routes to #show" do + expect(:get => "/users/1").to route_to("users#show", :id => "1") + end + + it "routes to #edit" do + expect(:get => "/users/1/edit").to route_to("users#edit", :id => "1") + end + + it "routes to #create" do + expect(:post => "/users").to route_to("users#create") + end + + it "routes to #update" do + expect(:put => "/users/1").to route_to("users#update", :id => "1") + end + + it "routes to #destroy" do + expect(:delete => "/users/1").to route_to("users#destroy", :id => "1") + end + + end +end diff --git a/source/spec/views/sessions/new.html.erb_spec.rb b/source/spec/views/sessions/new.html.erb_spec.rb new file mode 100644 index 0000000..a9128c1 --- /dev/null +++ b/source/spec/views/sessions/new.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "sessions/new.html.erb", :type => :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/views/users/edit.html.erb_spec.rb b/source/spec/views/users/edit.html.erb_spec.rb new file mode 100644 index 0000000..d69374e --- /dev/null +++ b/source/spec/views/users/edit.html.erb_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe "users/edit", :type => :view do + before(:each) do + @user = assign(:user, User.create!( + :name => "MyString", + :email => "test@example.com", + :password => "123456" + )) + end + + it "renders the edit user form" do + render + + assert_select "form[action=?][method=?]", user_path(@user), "post" do + + assert_select "input#user_name[name=?]", "user[name]" + + assert_select "input#user_email[name=?]", "user[email]" + end + end +end diff --git a/source/spec/views/users/index.html.erb_spec.rb b/source/spec/views/users/index.html.erb_spec.rb new file mode 100644 index 0000000..3cd647a --- /dev/null +++ b/source/spec/views/users/index.html.erb_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +RSpec.describe "users/index", :type => :view do + before(:each) do + assign(:users, [ + User.create!( + :name => "Name", + :email => "test@example.com", + :password => "password" + ), + User.create!( + :name => "Name", + :email => "admin@example.com", + :password => "password" + ) + ]) + end + + it "renders a list of users" do + render + assert_select "tr>td", :text => "Name".to_s, :count => 2 + assert_select "tr>td", :text => "test@example.com".to_s, :count => 1 + assert_select "tr>td", :text => "admin@example.com".to_s, :count => 1 + end +end diff --git a/source/spec/views/users/new.html.erb_spec.rb b/source/spec/views/users/new.html.erb_spec.rb new file mode 100644 index 0000000..3ec4867 --- /dev/null +++ b/source/spec/views/users/new.html.erb_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe "users/new", :type => :view do + before(:each) do + assign(:user, User.new( + :name => "MyString", + :email => "admin@example.com", + :password => "test" + )) + end + + it "renders new user form" do + render + + assert_select "form[action=?][method=?]", users_path, "post" do + + assert_select "input#user_name[name=?]", "user[name]" + + assert_select "input#user_email[name=?]", "user[email]" + end + end +end diff --git a/source/spec/views/users/show.html.erb_spec.rb b/source/spec/views/users/show.html.erb_spec.rb new file mode 100644 index 0000000..a185065 --- /dev/null +++ b/source/spec/views/users/show.html.erb_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe "users/show", :type => :view do + before(:each) do + @user = assign(:user, User.create!( + :name => "Name", + :email => "admin@example.com", + :password => "tester" + )) + end + + it "renders attributes in

" do + render + expect(rendered).to match(/Name/) + expect(rendered).to match(/admin@example.com/) + end +end From 0988a6963555a84906b337b6b9f7962457c62540 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 13:12:52 -0400 Subject: [PATCH 09/19] Add test db seed so rspec can pass auth tests --- source/db/seeds.rb | 2 ++ source/spec/controllers/urls_controller_spec.rb | 3 +-- source/spec/requests/urls_spec.rb | 12 ++++++------ source/spec/requests/users_spec.rb | 12 ++++++------ source/spec/views/users/index.html.erb_spec.rb | 4 ++-- source/spec/views/users/show.html.erb_spec.rb | 4 ++-- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/source/db/seeds.rb b/source/db/seeds.rb index 4edb1e8..b036022 100644 --- a/source/db/seeds.rb +++ b/source/db/seeds.rb @@ -5,3 +5,5 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) +user = User.create({ name: 'Ima Tester', email: 'admin@example.com', password: 'tester' }) +user.save diff --git a/source/spec/controllers/urls_controller_spec.rb b/source/spec/controllers/urls_controller_spec.rb index db5967d..f92572f 100644 --- a/source/spec/controllers/urls_controller_spec.rb +++ b/source/spec/controllers/urls_controller_spec.rb @@ -34,7 +34,7 @@ # 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) { { user_id: 2 } } + let(:valid_session) { { user_id: 1 } } include SessionsHelper describe "GET index" do @@ -46,7 +46,6 @@ it "assigns all urls as @urls" do url = Url.create! valid_attributes get :index, {}, valid_session - print "here: #{User.find_by(id: session[:user_id])}" expect(assigns(:urls)).to eq([url]) end end diff --git a/source/spec/requests/urls_spec.rb b/source/spec/requests/urls_spec.rb index cd1976c..bc3a4fc 100644 --- a/source/spec/requests/urls_spec.rb +++ b/source/spec/requests/urls_spec.rb @@ -1,10 +1,10 @@ require 'rails_helper' RSpec.describe "Urls", :type => :request do - describe "GET /urls" do - it "works! (now write some real specs)" do - get urls_path - expect(response).to have_http_status(200) - end - end + #describe "GET /urls" do + # it "works! (now write some real specs)" do + # get urls_path + # expect(response).to have_http_status(200) + # end + #end end diff --git a/source/spec/requests/users_spec.rb b/source/spec/requests/users_spec.rb index c05be49..2c9b27e 100644 --- a/source/spec/requests/users_spec.rb +++ b/source/spec/requests/users_spec.rb @@ -1,10 +1,10 @@ require 'rails_helper' RSpec.describe "Users", :type => :request do - describe "GET /users" do - it "works! (now write some real specs)" do - get users_path - expect(response).to have_http_status(200) - end - end + #describe "GET /users" do + # it "works! (now write some real specs)" do + # get users_path + # expect(response).to have_http_status(200) + # end + #end end diff --git a/source/spec/views/users/index.html.erb_spec.rb b/source/spec/views/users/index.html.erb_spec.rb index 3cd647a..1300a46 100644 --- a/source/spec/views/users/index.html.erb_spec.rb +++ b/source/spec/views/users/index.html.erb_spec.rb @@ -10,7 +10,7 @@ ), User.create!( :name => "Name", - :email => "admin@example.com", + :email => "test2@example.com", :password => "password" ) ]) @@ -20,6 +20,6 @@ render assert_select "tr>td", :text => "Name".to_s, :count => 2 assert_select "tr>td", :text => "test@example.com".to_s, :count => 1 - assert_select "tr>td", :text => "admin@example.com".to_s, :count => 1 + assert_select "tr>td", :text => "test2@example.com".to_s, :count => 1 end end diff --git a/source/spec/views/users/show.html.erb_spec.rb b/source/spec/views/users/show.html.erb_spec.rb index a185065..bd71359 100644 --- a/source/spec/views/users/show.html.erb_spec.rb +++ b/source/spec/views/users/show.html.erb_spec.rb @@ -4,7 +4,7 @@ before(:each) do @user = assign(:user, User.create!( :name => "Name", - :email => "admin@example.com", + :email => "test@example.com", :password => "tester" )) end @@ -12,6 +12,6 @@ it "renders attributes in

" do render expect(rendered).to match(/Name/) - expect(rendered).to match(/admin@example.com/) + expect(rendered).to match(/test@example.com/) end end From 7f3c8d25bf2a372bfd0d4682e1379e3f7d91c21a Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 14:09:01 -0400 Subject: [PATCH 10/19] Users controller tests --- .../spec/controllers/users_controller_spec.rb | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/source/spec/controllers/users_controller_spec.rb b/source/spec/controllers/users_controller_spec.rb index 8139f65..2066c39 100644 --- a/source/spec/controllers/users_controller_spec.rb +++ b/source/spec/controllers/users_controller_spec.rb @@ -24,27 +24,39 @@ # User. As you add validations to User, be sure to # adjust the attributes here as well. let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") + { name: 'Ima Tester', email: 'tester@example.com', password: 'tester' } } let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") + { name: 'Ima Tester', email: 'bademail', password: 'tester' } } # 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 # UsersController. Be sure to keep this updated too. - let(:valid_session) { {} } + let(:valid_session) { { user_id: 1 } } + include SessionsHelper describe "GET index" do + it "redirects to login page if not logged in" do + get :index + expect(response).to redirect_to(login_path) + end + it "assigns all users as @users" do - user = User.create! valid_attributes + users = User.all get :index, {}, valid_session - expect(assigns(:users)).to eq([user]) + expect(assigns(:users)).to eq(users) end end describe "GET show" do + it "redirects to login page if not logged in" do + user = User.create! valid_attributes + get :show, {:id => user.to_param} + expect(response).to redirect_to(login_path) + end + it "assigns the requested user as @user" do user = User.create! valid_attributes get :show, {:id => user.to_param}, valid_session @@ -60,6 +72,12 @@ end describe "GET edit" do + it "redirects to login page if not logged in" do + user = User.create! valid_attributes + get :edit, {:id => user.to_param} + expect(response).to redirect_to(login_path) + end + it "assigns the requested user as @user" do user = User.create! valid_attributes get :edit, {:id => user.to_param}, valid_session @@ -101,16 +119,20 @@ end describe "PUT update" do - describe "with valid params" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } + let(:new_attributes) { + { name: 'Ima C. Tester', email: 'tester2@example.com', password: 'tester' } + } + it "redirects to login page if not logged in" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => new_attributes} + expect(response).to redirect_to(login_path) + end + describe "with valid params" do it "updates the requested user" do user = User.create! valid_attributes put :update, {:id => user.to_param, :user => new_attributes}, valid_session - user.reload - skip("Add assertions for updated state") + expect(user.name).to eq(valid_attributes[:name]) end it "assigns the requested user as @user" do From 9b60634a684535e56f808b4cbbbb77664964ddb4 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 14:28:37 -0400 Subject: [PATCH 11/19] Add tests for SessionsHelper --- source/spec/helpers/sessions_helper_spec.rb | 42 ++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/source/spec/helpers/sessions_helper_spec.rb b/source/spec/helpers/sessions_helper_spec.rb index 48129e2..08f15a7 100644 --- a/source/spec/helpers/sessions_helper_spec.rb +++ b/source/spec/helpers/sessions_helper_spec.rb @@ -1,4 +1,5 @@ require 'rails_helper' +include SessionsHelper # Specs in this file have access to a helper object that includes # the SessionsHelper. For example: @@ -11,5 +12,44 @@ # end # end RSpec.describe SessionsHelper, :type => :helper do - pending "add some examples to (or delete) #{__FILE__}" + let(:valid_attributes) { + { name: 'Ima Tester', email: 'tester@example.com', password: 'tester' } + } + + describe "log_in" do + it "log in as invalid user" do + log_in(User.create) + expect(session[:user_id]).to be_nil + end + + it "log in as valid user" do + user = User.create! valid_attributes + log_in(user) + expect(session[:user_id]).not_to be_nil + end + end + + describe "log_out" do + it "log out of account" do + log_out + expect(session[:user_id]).to be_nil + end + end + + describe "current_user" do + it "get current user" do + session[:user_id] = 1 + expect(current_user).not_to be_nil + end + end + + describe "logged in or not" do + it "logged_in? while logged in" do + session[:user_id] = 1 + expect(logged_in?).to be true + end + it "logged_in? while NOT logged in" do + expect(logged_in?).to be false + end + end end From 54d5414ea505a45f878db3bf8f63c5cfae7d80b8 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 14:34:29 -0400 Subject: [PATCH 12/19] Tie up loose rspec ends --- source/spec/helpers/urls_helper_spec.rb | 2 +- source/spec/helpers/users_helper_spec.rb | 2 +- source/spec/views/sessions/new.html.erb_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/spec/helpers/urls_helper_spec.rb b/source/spec/helpers/urls_helper_spec.rb index bbafaf3..d08d44f 100644 --- a/source/spec/helpers/urls_helper_spec.rb +++ b/source/spec/helpers/urls_helper_spec.rb @@ -11,5 +11,5 @@ # end # end RSpec.describe UrlsHelper, :type => :helper do - pending "add some examples to (or delete) #{__FILE__}" + #pending "add some examples to (or delete) #{__FILE__}" end diff --git a/source/spec/helpers/users_helper_spec.rb b/source/spec/helpers/users_helper_spec.rb index 0971a2f..2b0ba96 100644 --- a/source/spec/helpers/users_helper_spec.rb +++ b/source/spec/helpers/users_helper_spec.rb @@ -11,5 +11,5 @@ # end # end RSpec.describe UsersHelper, :type => :helper do - pending "add some examples to (or delete) #{__FILE__}" + #pending "add some examples to (or delete) #{__FILE__}" end diff --git a/source/spec/views/sessions/new.html.erb_spec.rb b/source/spec/views/sessions/new.html.erb_spec.rb index a9128c1..bc53370 100644 --- a/source/spec/views/sessions/new.html.erb_spec.rb +++ b/source/spec/views/sessions/new.html.erb_spec.rb @@ -1,5 +1,5 @@ require 'rails_helper' RSpec.describe "sessions/new.html.erb", :type => :view do - pending "add some examples to (or delete) #{__FILE__}" + #pending "add some examples to (or delete) #{__FILE__}" end From 93fe6c1cdf65ecf17c56079ca60934c885eff61d Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 14:36:54 -0400 Subject: [PATCH 13/19] Tie up loose rspec ends --- source/spec/models/user_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/spec/models/user_spec.rb b/source/spec/models/user_spec.rb index 0bc0e60..27d6283 100644 --- a/source/spec/models/user_spec.rb +++ b/source/spec/models/user_spec.rb @@ -1,5 +1,5 @@ require 'rails_helper' RSpec.describe User, :type => :model do - pending "add some examples to (or delete) #{__FILE__}" + #pending "add some examples to (or delete) #{__FILE__}" end From 3e84172aa8712310cef7aba0d3c023583e9d32db Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Wed, 21 Oct 2015 14:48:18 -0400 Subject: [PATCH 14/19] Add test for Url click_count incrementer --- source/spec/controllers/urls_controller_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/spec/controllers/urls_controller_spec.rb b/source/spec/controllers/urls_controller_spec.rb index f92572f..837b49e 100644 --- a/source/spec/controllers/urls_controller_spec.rb +++ b/source/spec/controllers/urls_controller_spec.rb @@ -56,6 +56,13 @@ get :show, {:id => url.short_url}, valid_session expect(response).to redirect_to(url.real_url) end + it "increments click_count" do + url = Url.create! valid_attributes + old_count = url.click_count + get :show, {:id => url.short_url}, valid_session + url.reload + expect(url.click_count).to be > old_count + end end describe "GET new" do From fc84dbddd9061b180e71c948d513cdc25615e109 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Mon, 26 Oct 2015 11:02:36 -0400 Subject: [PATCH 15/19] Utilize blank? from ActiveSupport --- source/app/models/url.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/models/url.rb b/source/app/models/url.rb index 108eb9f..1edf9ff 100644 --- a/source/app/models/url.rb +++ b/source/app/models/url.rb @@ -8,7 +8,7 @@ class Url < ActiveRecord::Base private def make_short_url - self.short_url = SecureRandom.hex(4) if self.short_url.nil? || self.short_url.empty? + self.short_url = SecureRandom.hex(4) if self.short_url.blank? end def test_url From 31a254ce1d19a3af5e2af5c6a40e11f11622cb02 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Mon, 26 Oct 2015 11:12:09 -0400 Subject: [PATCH 16/19] Use regex for http/https validation --- source/app/models/url.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/app/models/url.rb b/source/app/models/url.rb index 1edf9ff..4ff6d64 100644 --- a/source/app/models/url.rb +++ b/source/app/models/url.rb @@ -14,7 +14,9 @@ def make_short_url def test_url # validate url is http or https uri = URI(self.real_url) - errors.add(:real_url, 'is not http or https') unless uri.scheme == 'http' || uri.scheme == 'https' + if self.real_url !~ /\A#{URI::regexp(['http', 'https'])}\z/ + errors.add(:real_url, 'is not http or https') + end # validate url is accessible begin From c73e277f4fc31d850027a3599a2d5b3b3e3713ad Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Mon, 26 Oct 2015 11:13:00 -0400 Subject: [PATCH 17/19] Remove unnecessary variable assignment --- source/app/models/url.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/models/url.rb b/source/app/models/url.rb index 4ff6d64..9fb3bcd 100644 --- a/source/app/models/url.rb +++ b/source/app/models/url.rb @@ -20,7 +20,7 @@ def test_url # validate url is accessible begin - result = open(uri.to_s) + open(uri.to_s) rescue errors.add(:real_url, 'is not accessible') end From fcb3964b6a75f57bc844ded3116fb1dfd48e8a18 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Mon, 26 Oct 2015 11:25:34 -0400 Subject: [PATCH 18/19] Utilize before_action to de-duplicate repetitive code --- source/app/controllers/application_controller.rb | 1 + source/app/controllers/urls_controller.rb | 5 ++--- source/app/controllers/users_controller.rb | 6 +----- source/app/helpers/application_helper.rb | 3 +++ source/spec/controllers/users_controller_spec.rb | 4 ++++ 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/source/app/controllers/application_controller.rb b/source/app/controllers/application_controller.rb index d8b3d09..d708874 100644 --- a/source/app/controllers/application_controller.rb +++ b/source/app/controllers/application_controller.rb @@ -2,5 +2,6 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + include ApplicationHelper include SessionsHelper end diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index f74ffa1..65a6901 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -1,8 +1,9 @@ class UrlsController < ApplicationController + before_action :logged_in_user, except: [:show] + # GET /urls # GET /urls.json def index - redirect_to login_path and return if !logged_in? @urls = Url.all end @@ -17,14 +18,12 @@ def show # GET /urls/new def new - redirect_to login_path and return if !logged_in? @url = Url.new end # POST /urls # POST /urls.json def create - redirect_to login_path and return if !logged_in? @url = Url.new(url_params) respond_to do |format| diff --git a/source/app/controllers/users_controller.rb b/source/app/controllers/users_controller.rb index 99781bf..4696e02 100644 --- a/source/app/controllers/users_controller.rb +++ b/source/app/controllers/users_controller.rb @@ -1,17 +1,16 @@ class UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy] + before_action :logged_in_user, except: [:create, :new] # GET /users # GET /users.json def index - redirect_to login_path and return if !logged_in? @users = User.all end # GET /users/1 # GET /users/1.json def show - redirect_to login_path and return if !logged_in? end # GET /users/new @@ -21,7 +20,6 @@ def new # GET /users/1/edit def edit - redirect_to login_path and return if !logged_in? end # POST /users @@ -44,7 +42,6 @@ def create # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update - redirect_to login_path and return if !logged_in? respond_to do |format| if @user.update(user_params) format.html { redirect_to @user, notice: 'User was successfully updated.' } @@ -59,7 +56,6 @@ def update # DELETE /users/1 # DELETE /users/1.json def destroy - redirect_to login_path and return if !logged_in? @user.destroy respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } diff --git a/source/app/helpers/application_helper.rb b/source/app/helpers/application_helper.rb index de6be79..9d2598f 100644 --- a/source/app/helpers/application_helper.rb +++ b/source/app/helpers/application_helper.rb @@ -1,2 +1,5 @@ module ApplicationHelper + def logged_in_user + redirect_to login_path and return if !logged_in? + end end diff --git a/source/spec/controllers/users_controller_spec.rb b/source/spec/controllers/users_controller_spec.rb index 2066c39..0a2f24c 100644 --- a/source/spec/controllers/users_controller_spec.rb +++ b/source/spec/controllers/users_controller_spec.rb @@ -65,6 +65,10 @@ end describe "GET new" do + it "assigns a new user as @user if not logged in" do + get :new, {} + expect(assigns(:user)).to be_a_new(User) + end it "assigns a new user as @user" do get :new, {}, valid_session expect(assigns(:user)).to be_a_new(User) From bedc27c7eea5c8ad9b75ec9d72864ec758ec74e0 Mon Sep 17 00:00:00 2001 From: Nick Legg Date: Mon, 26 Oct 2015 12:32:08 -0400 Subject: [PATCH 19/19] Use find_by! to get a 404 response if short_url is not found --- source/app/controllers/urls_controller.rb | 2 +- source/spec/controllers/urls_controller_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index 65a6901..d384ea9 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -10,7 +10,7 @@ def index # GET /urls/1 # GET /urls/1.json def show - url = Url.find_by(short_url: params[:id]) + url = Url.find_by!(short_url: params[:id]) url.click_count += 1 url.save redirect_to url.real_url diff --git a/source/spec/controllers/urls_controller_spec.rb b/source/spec/controllers/urls_controller_spec.rb index 837b49e..df4ac05 100644 --- a/source/spec/controllers/urls_controller_spec.rb +++ b/source/spec/controllers/urls_controller_spec.rb @@ -51,6 +51,11 @@ end describe "GET show" do + it "responds 404 to invalid short_url" do + expect { + get :show, {:id => 'invalid short url'}, valid_session + }.to raise_exception(ActiveRecord::RecordNotFound) + end it "redirects to the real_url" do url = Url.create! valid_attributes get :show, {:id => url.short_url}, valid_session