## URL shortner [:bulb:Youtube](https://www.youtube.com/playlist?list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc) >1~4 build link model, url shortener model with RSpec >5 build link controller with RSpec >6 build the controller Home (homepage we land on:) >7 validates user entry url format[skipped] >8 make the shortlink in link.rb and create.js.erb >9 styling with bootstrap[skipped] new a project :) make sure webpacker, net-smtp, RSpec is bundled in the Gemfile. :bulb: [RSpec](https://github.com/rspec/rspec-rails/tree/6-0-maintenance) --- # Model part ---- # Write url shortener model and spec > Go to `spec`, and create a folder [services], in the file you create a `urlshort_spec.rb` > > In here you will create the TDD Tests, there are 3 logic tests in this spec. ```ruby! require 'rails_helper' RSpec.describe Urlshort do it "shortens a URL to 7 character like bit.ly" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" urlshort = Urlshort.new(url) expect(urlshort.lookup_code.length).to eq(7) end it "gives each URL its own lookup code" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" urlshort = Urlshort.new(url) code1 = urlshort.lookup_code url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWabcd" urlshort = Urlshort.new(url) code2 = urlshort.lookup_code expect(code2).not_to eq(code1) end it "always gives the same lookup code to the same URL" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" urlshort = Urlshort.new(url) code = urlshort.lookup_code url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" urlshort = Urlshort.new(url) same_code = urlshort.lookup_code expect(code).to eq(same_code) end it "it generates a Link record with a unique lookup code" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" urlshort = Urlshort.new(url) link = urlshort.generate_short_link expect(link.valid?).to eq(true) end end ``` > Go to `app`, and create a folder [services], in the file you create a `urlshort.rb` > > > In here you will create the class and functions that in spec file you use ```ruby! require 'digest/sha2' class Urlshort attr_reader :url, :link_model def initialize(url, link_model = Link) @url = url @link_model = link_model end def lookup_code Digest::SHA256.hexdigest(url)[0..6] end def generate_short_link link_model.create(url: url, lookup_code: lookup_code) end end ``` --- # Write link model and specs > Go to terminal, ```shell! rails g model Link lookup_code:string url:string ``` ```shell! rails db:migrate ``` > Go to `spec/models/link_spec.rb` > > In here you will create the TDD Tests, valid if there are url and lookup code, invalid if lost either of them. ```ruby! require 'rails_helper' RSpec.describe Link, type: :model do it "is valid if always has a URL and always has a lookup code" do link = Link.new( url: "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc", lookup_code: "1234567" ) expect(link.valid?).to be(true) end it "is invalid if no url" do link = Link.new( lookup_code: "1234567" ) expect(link.valid?).to be(false) end it "is invalid if no lookup code" do link = Link.new( url: "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" ) expect(link.valid?).to be(false) end it "is invalid if lookup code is not unique" do link = Link.new( url: "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc", lookup_code: "1234567" ) link.save link2 = Link.new( url: "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWabcd", lookup_code: "1234567" ) expect(link2.valid?).to be(false) end end ``` > Go to `models/link.rb` > > In here you will create the class to pass your test. ```ruby! class Link < ApplicationRecord validates_presence_of :lookup_code, :url validates_uniqueness_of :lookup_code end ``` :::success :bulb:Dependency injection: in the model.rb, you can refer to another class with capital, like the `Link` in `urlshort.rb` ::: :::success :bulb:Lookup_code method logic 1. Get a lookup code 2. Check with the loop if it already exist 3. Break out of the loop if it does not exist 4. If not, get another lookup code ::: > Go to `urlshort.rb` > > > In here you will create the method based on above logic ```ruby! def lookup_code i = 0 loop do code = get_fresh_code(i) break code unless link_model.exists?(lookup_code: code) i = i + 1 end end private #make a get_fresh_code method so you can change your algorithm of generating it anytime you want if not Digest::SHA256 def get_fresh_code(i) Digest::SHA256.hexdigest(url)[i..(i + 6)] end ``` :::success :bulb: get_fresh_code method logic if you go to rails console, and type in SecureRandom, this is a library that get a unique user id. >SecureRandom.uuid, you can get random charaters, so you can use this instead od Digest::SHA256 for generating random characters for the short link ::: ![](https://i.imgur.com/8Zm7tig.png) > Go to `urlshort.rb` > > > In here you will create the method based on above logic ```ruby! def lookup_code loop do code = get_fresh_code break code unless link_model.exists?(lookup_code: code) end end private #use library SecureRandom def get_fresh_code SecureRandom.uuid[0..6] end ``` > Go to `urlshort_spec.rb` > Delete this spec because it no longer serves the purpose ```ruby! it "always gives the same lookup code to the same URL" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" urlshort = Urlshort.new(url) code = urlshort.lookup_code url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" urlshort = Urlshort.new(url) same_code = urlshort.lookup_code expect(code).to eq(same_code) end ``` ---- # Controller part --- # Link Controller > Go to `spec`, and create a folder [controllers], in the file you create a `links_controller_spec.rb` ```ruby! require 'rails_helper' RSpec.describe LinksController, type: :controller do it "can shorten a link provided by the user" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" post :create, params: { link: {url: url}} end end ``` :heavy_check_mark: checking terminal `rspec`, know that you need to create controller ```shell rspec ``` > Go to `controllers`, create file `links_controller.rb` ```ruby! class LinksController < ApplicationController end ``` :heavy_check_mark: checking rspec, know that you need to create routes => No route matches {:action=>"/links > Go to `routes.rb` ```ruby! Rails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html post "/links" => 'links#create' end ``` :heavy_check_mark: checking rspec, know that you need to create `create` action in link controller >Go to `links_controller.rb` ```ruby! class LinksController < ApplicationController def create end end ``` >Go to `links_controller_spec.rb` ```ruby! require 'rails_helper' RSpec.describe LinksController, type: :controller do it "can shorten a link provided by the user" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" post :create, params: { link: {url: url}} #assigns: looking for instance variable that already has been set link = assigns(:link) expect(link.url).to eq (url) expect(link.valid?).to be (true) expect(link.lookup_code.length).to eq (7) end end ``` :heavy_check_mark: checking rspec, know that you need to add gem `rails-controller-testing` in the gemfile, and run `bundle` in terminal :bulb: `assigns`: looking for instance variable that already has been set >Go to `links_controller.rb` ```ruby! class LinksController < ApplicationController def create url = link_params[:url] urlshort = Urlshort.new(url) @link = urlshort.generate_short_link end private def link_params params.require(:link).permit(:url) end endx ``` --- # Views part >Go to `views`, create a folder `links`, and create a file `create.js.erb` >Go to `links_controller_spec.rb` >Add request.env["HTTP_ACCEPT"] = "text/javascript" >Add expect(response).to render_template('create') >Add expect(link.persisted?).to be (true) ```ruby! require 'rails_helper' RSpec.describe LinksController, type: :controller do it "can shorten a link provided by the user" do url = "https://www.youtube.com/watch?v=ZBs1a6Y25bY&list=PLjItgYqIzJ9WGy9WMf-44DXHB_7aWwJMc" request.env["HTTP_ACCEPT"] = "text/javascript" post :create, params: { link: {url: url}} #assigns: looking for instance variable that already has been set link = assigns(:link) expect(link.url).to eq (url) expect(link.valid?).to be (true) expect(link.lookup_code.length).to eq (7) expect(link.persisted?).to be (true) expect(response).to render_template('create') end end ``` --- >Go to terminal, we are going to build a page! >This will set up a controller, a page, and routes for us ```shell! rails g controller Home index ``` >Go to routes, change the get to root to: ```ruby! Rails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html # get 'home/index' root to:'home#index' post "/links" => 'links#create' end ``` :heavy_check_mark: you should be able to see this ![](https://i.imgur.com/k9uKAkn.png) >Go to `home_controller.rb` ```ruby! ``` >Go to `app/views/home/index.html.erb` >let's build a form for the url shortener! ```erb <h1>Destly- A url shotener</h1> <%= form_with model: @link, remote: true do |form| %> <%= form.text_field :url %> <%= form.submit "create shortlink"%> <% end %> ``` :heavy_check_mark: You should be able to see this ![](https://i.imgur.com/sccOV3z.png) >Go to `create.js.erb` ```javascript! var lookupCode = "<%= @link.lookup_code %>;" console.log(lookupCode) ``` :::success :bulb: so far all the entry can generate a 7 character url short, we need gem pry-rails, so add that in dev `Gemfile` and `bundle` ::: >Go to `links_controller.rb` >Add binding.pry ```ruby! class LinksController < ApplicationController def create url = link_params[:url] urlshort = Urlshort.new(url) @link = urlshort.generate_short_link binding.pry end private def link_params params.require(:link).permit(:url) end endx ``` --- # Render the short link :heavy_check_mark: let's add the short link to the home page >Go to `index.html.erb` ```htmlembedded! <h1>Destly- A url shotener</h1> <%= form_with model: @link, remote: true do |form| %> <%= form.text_field :url %> <%= form.submit "Create Shortlink"%> <% end %> <div id="url-shortlink"> </div> ``` >Go to `create.js.erb` ```javascript! var lookupCode = "<%= @link.lookup_code %>;" console.log(lookupCode) var element = document.getElementById("url-shortlink") element.innerHTML = lookupCode ``` --- # Redirecting to the original url >Go to spec and create a folder `requests`, create a file `link_redirect_spec.rb`