## 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
:::

> 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

>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

>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`