1、WishList製作功能 - CR

1-1、第零步驟:路徑設定routes

來做許願頁面,先給出許願頁面的首頁、new、create功能

> get "/make_a_wish", to: "wish_list#card"              # 許願首頁
> get "/new_wish", to: "wish_list#new_wish"             # 許願新增頁
> post "/wish_card_list", to: "wish_list#create_wish"   # 許願新增頁的表單提交地方

> Verb "網址", to: "controller#action"                   # 此為routes格式 

1-2、第一步驟:新增表單new form

到new頁面的把form功能做出來

> <form action="/wish_card_list" method="post">
>     <input type="text" name="title">
>     <textarea name="description" id="" cols="30" rows="10"></textarea>
>     <button>送出</button>
> </form>

1-2-1、form action

form 的 action 是指表單送出後,要把資料送去的地方

1-2-2、form method

form 的 method 的 預設是get


get和post的差異
用get的方式送,欄位的name名稱會轉成queryString,並傳遞給下一個頁面
用post的方式送,input的name參數,不會顯示在queryString上
如果今天做登入功能,不能用get方法傳,圖片之類的也是,要要用post傳
Ps.post只是沒有顯示input資料在網址上,如果今天用console抓,還是抓的到


1-2-3、input name


input會被抓到的是name值很重要,後端抓的是這個值


1-3、第二步驟:authenticity_token介紹

接著到wish_list的controller,我們把表單input送過來的東西印出來

> def create_wish
>  render html: params              # 這裡直接 params 就可以抓到那一包
> end

不過這時候會發生一個錯誤,authenticity_token,這個是什麼呢??
其實這個就是rails的安全系統,假設你今天表單送出的token,是正常管道送進去
並且有遵守自己檔案內key值產生的原則,系統就會讓你送進資料庫


ActionController::InvalidAuthenticityToken
這個是預設打開的,如果表單有送出token,並且是正常管道送出去的,rails就讓你通過



authenticity_token -> config檔案裡面有一個master.key檔案,裡面的key非常重要,key掉了,就爆了


1-4、第三步驟:解決authenticity_token

在form表單,加上一個input,就可以讓資料表做token驗證(如果屬性不給hidden,他會顯示在畫面上)

> <input type="hidden" name="authenticity_token">

我們實際把表單送出的東西,印出之後就是這一大串,可以發現就是一大串亂碼

> render html: params           # 把表單送過來的資料印出來

> {"authenticity_token"=>"S57jwgxx7R4vr0g0iBRHMd7dIbcc9Ept980Rr9dmgRKMMkhHAv8JJS9YaVbOuVrT0hVJWJulQLwZ2RRCi_hXRw", "title"=>"sdf", "content"=>"qwe", "controller"=>"wish_list", "action"=>"create_wish"}

1-5、第四步驟:表單input name用中括號包起來

把input欄位的值,前面加上一個字串,wish_list[title]這樣可以用wish_list把中括弧裡面的東西變成hash格式

  <form action="/wish_card_list" method="post">
      <input type="hidden" name="authenticity_token">

>     <input type="text" name="wish_list[title]">                                   -> 原本是title
>     <textarea name="wish_list[description]" id="" cols="30" rows="10"></textarea> -> 原本是description

      <button>送出</button>
  </form>

上面兩個name值更改後,用檢查工具查看網頁html,會發現表單的input欄位會變這樣

> title、content被wish_list包成一個hash了 
> "wish_list"=>{"title"=>"sdfdsf", "content"=>"eqwe"}

如果實際印出來

> render html: params[:wish_list]           # 把[:wish_list]印出來

> {"title"=>"sdfdsf", "content"=>"eqwe"}

1-6、第五步驟:ForbiddenAttributesError介紹

因為剛剛改過form的input後,我們可以一次抓取input的東西,把這一包塞進表單中

> wish_list = WishList.new(params[:wish_list])

> params[:wish_list]剛剛有解釋了,就是因為在表單的name值那邊有動手腳

但!!!這樣寫之後,跑出了另外一個error => ForbiddenAttributesError
這個東西是什麼呢?
假設今天有奇怪的欄位(容易被猜中亂搞的欄位),惡意攻擊者可以直接在html那邊寫入資料,這樣就可以搞亂你的資料庫了
又因為(params[:wish_list] -> 這個是一大包的),所以假如今天有人偷偷把資料塞進這一包,是塞得進來的
所以rails做了一個安全機制,什麼安全機制呢?就是你要匯進去的欄位,要精確的指定

1-7、第六步驟:ForbiddenAttributesError解決方法

要怎麼解決這個問題呢??rails 有一個再一次驗證的寫法
permit後面要接的,就是允許匯進資料庫的欄位

clean_params = params.require(:wish_list).permit(:title, :description)

可以把clean_params印出來看看

> render html: clean_params

> "title"=>"sdfdsf", "content"=>"eqwe" => 他只會印出上面permit裡包含的欄位

params.require Vs. params[:wish_list]
用 params.require 的原因是,require會做額外的檢查
要不然用params[:wish_list]也可以
clean_params 這個專有名詞叫做 Strong Parameters、也就是一個白名單的概念


1-8、第七步驟:資料成功送進資料庫

把前面的安全機制都設定好後,就可以成功把資料送進資料庫了

# create action 

clean_params = params.require(:wish_list).permit(:title, :description)
wish_list = WishList.new(clean_params)

if wish_list.save
  redirect_to make_a_wish_path
else
  render html: "fail"
end

1-9、第八步驟:flash[:notice]、flash[:alert]設定

接下來我們來設定,當成功送出、失敗時,要顯示的訊息
首先到application.html,公版的位置,把flash的顯示條件設定好

> <% if flash.any? %>       
>     <div style = "background-color: green; color: white">
>         <%= flash[:notice] %>
>     </div>
>     <div style = "background-color: red; color: white">
>       <%= flash[:alert] %>
>     </div>
> <% end %>

之後到controller把flash功能加上去

> if wish_list.save
>     redirect_to make_a_wish_path, notice: "success new wish_list" -> 這個notice是簡寫,和redirect一起寫
> else

1-10、第九步驟:輸入欄位驗證

可以在想要限制model某個欄位的值,是否一定要輸入才能送出,還有很多類型的驗證方式,之後有機會再補充

> validates :title, presence: true              # 這個意思是,title欄位一定要填寫
> validates :description, presence: true        # 這個意思是,description欄位一定要填寫

1-11、第十步驟:表單改寫 form_helper

前面表單產生太囉唆了,要產生token,又要把input name中的東西包起來,因此我們來用form_helper來寫

> 1. form_tag => form_tag(url)    > 做功能,不需要用到model,就用form_tag。ex. 做BMI計算際就不用model  
> 2. form_for => form_for(@model) > 做功能,不需要用到model,就用form_for。ex. 普通有做有model功能的表單,就是用此方法  
> 3. form_with => form_hash(HASH) > 融合上面兩種寫法  

1-11-1、for_for使用注意事項


(1) form_for 後面接的是一個model實體
(2) 如果今天url沒有填,rails會猜,你的路徑是跟前面給的WishList很像,他會猜wish_list_path,但是因為今天路徑是我們自己客製的,所以這裡的url要自己寫
(3) form_for後面要接的實體,不要在這邊new物件,先在controller寫完,並創一個實體變數,再傳給view用就好
(4) 今天這樣寫@wish_list = WishList.new(title: 123),title那個欄位一開始會有預設值123,可以傳過去的關係就是help幫你的


1-11-2、controller更改

controller更改,model.new實體在controller寫好,再傳給view

def new_wish
  @wish_list = WishList.new
end

1-11-3、view更改

view頁面更改,記得form_for後面第一個參數是model.new,第二個參數是url

> <%= form_for @wish_list, url: "/wish_card_list" do |form| %>
>   <%= form.label :title %>
>   <%= form.text_field :title %>
> 
>   <%= form.label :description %>
>   <%= form.text_area :description %>
> 
>   <%= form.submit %>
> <% end %>

1-11-4、form_with 使用注意事項

  1. 一定要寫model: @wishlist
  2. 在表單實體那邊,form_for 給nil就會噴錯,form_with給nil只會啥都不會印給你
  3. form_for在html那邊,多了一串id=”new_wish_list”(id=new的路徑)
> <%= form_with (model: @wishlist) do |form| %> .  => 表單實體就是@wishlist
>   <%= form.label :title %>
>   <%= form.text_field :title %>
> 
>   <%= form.label :description %>
>   <%= form.text_area :description %>
> <% end %>

1-12、第十一步驟:field_with_errors 欄位驗證

這個欄位是當你有設定欄位驗證時,假如你沒有符合驗證,就會跳出這個欄位
此時我們來改寫,如果create無法寫進資料庫(有寫validate),此時失敗的話,我們要重新渲染新增表單頁面

1-12-1、render redirect原地重導

> if @wish_list.save
>     redirect_to make_a_wish_path, notice: "success new wish_list"
> else
>     render :new_wish                      -> 如果存入資料庫失敗,就重新渲染網頁
> end

1-12-2、更改css

欄位錯誤的話,HTML會有多一個欄位叫做field_with_errors,針對錯誤欄位增加CSS

.field_with_errors {
    display: inline;    

    label[for="wish_list_title"] {
        color: red;
    }

    label[for="wish_list_description"] {
        color: red;
    }
    textarea[id="wish_list_description"] {
        border: 1px solid red;
    }

    input[id="wish_list_title"] {
        border: 1px solid red;
    }
}

1-13、第十二步驟:許願單的Read

接下來我們把model中的東西讀取出來
controller的部分,用實體變數接住model所有的數據

def card
  @wish_lists = WishList.all
end

view的部分,用迴圈把model中的所有數據印出來

> <% @wish_lists.each do |wish_list| %>
>   <li>
>     <%= link_to wish_list.title, wish_card_info_path(wish_list.id) %>
>   </li>
> <% end %>

1-14、第十三步驟:許願單的show

再來把各別筆數的所有資料印出來

controller的部分,用抓取個別連結的id

def show_wish
    @wish_list = WishList.find_by(id: params[:num])
end

這邊是params[:num]的關係,是因為我在routes設定跟resources不太一樣,注意get後面第一個字串
我是使用/:num,因此最後params抓到就是{id: params[:num]}

get "/wish_card_info/:num", to: "wish_list#show_wish", as: "wish_card_info"

這邊會在routes用到as的關係是因為,這次我們是都用客製的,所以path很亂,基本上不太會用到
如果以後有機會用到,其實他就是網址,仔細看他和get後面那一串很像,不過因為這一次我們亂打,
所以rails他在抓取的時候,會判斷錯誤,所以我們要再加寫path


上面那樣可能有點模糊,我們實際把params id抓到的東西印出來

> 網址連結 http://[::1]:3000/wish_card_info/9

到erb使用params把東西印出來,要抓的東西就是”num”=>”9”,這個hash中的值(9)

> <%= params %>

> 印出這一串 params資料 {"controller"=>"wish_list", "action"=>"show_wish", "num"=>"9"}

解釋完為啥是params[:num]後,就可以到view頁面把資料印出來

> <table>
>     <tr>
>         <td>wish_list ID</td>
>         <td>wish_list 姓名</td>
>         <td>wish_list 描述</td>
>     </tr>
>     <tr>
>         <td><%= @wish_list.id %></td>
>         <td><%= @wish_list.title %></td>
>         <td><%= @wish_list.description %></td>
>     </tr>
> </table>

2、rescue、rescue_from

11/17 - 11.05

如果今天顧客直接更改網址,找到超出資料庫的資料,會讓網頁爆炸

> 網址 : http://localhost:3000/wish_card_info/3464645757

2-1、使用rescue方法

第一種發生錯誤解法

因此我們要加一點東西,讓網頁不會爆炸

def create
    begin 
        @wish_list = WishList.find(params[:num])
        render html: @wish_list.title
    rescue
        render html: "找不到"
    end
end

2-2、使用rescue_from

第二種發生錯誤解法
不過因為報錯很常見,我們把這個報錯的寫法,拉高層級,寫到controller那邊,並且新增一個方法
這樣寫只要以後都有發生RecordNotFound錯誤,都會報錯

寫在整個controller最上面

rescue_from ActiveRecord::RecordNotFound, with: :not_found 

並把方法寫在private區塊 Ps. 如果錯誤,印出找不到,但是這樣不太對,應該要連到404頁面才對

private
def not_found
  render html: "找不到", status: 404
end

2-3、修改404頁面

從這個路徑,public > 404.html,就可以找到404.html,接著可以根據你想要的畫面,去修改404頁面

2-4、public資料夾說明


Q. 為什麼404、422、500會放在public資料夾,不像其他routes一樣,在那邊寫路徑就好了??
A. 因為如果今天網站壞掉的話,public裡的檔案會先查看,不用進routes查看路徑,就可以解決了


放在public的東西,基本就是不需要要進routes的檔案
ex. favicon, robots.text, 404, 500(robots那個是給爬蟲看的)

最後利用rails特有語法Rails.root(rails內的檔案),讓我們可以連結到404網頁,並且記得帶404狀態給他
不過這時候如果開html看,會發現layout會和404頁面都存在,所以記得在這邊設定layout: false

def record_not_found
    render file: "#{Rails.root}/public/404.html", status: 404, layout: false
end

3、show 頁面的路徑演化

非常重要,以下看完可以知道,原本用a-link連到不同id的內頁,後面是怎麼變成由.format方式的
教學影音在ruby 11/15 最後一個影片的20:00 筆記要記得做完,了解這個流程,對以後queryString很有幫助

接著到view頁面,想要點下連結可以跳轉到每個實體裡面的內容時

3-1、首先我們先設定路徑

目前路徑還是錯的,後面會轉成正確的

get "/wish_list_info", to: "wishlist#show"

接著我們把路徑連結放的view頁面,並用a-link連過去

> <a href="/wish_list_info"><%= @wishlist.title %></a>

不過這樣設定會有一個問題,每個實體點了這個連結,導過去都是同一個連結(我想要達成的是,連結後方要帶著各自的id)
因此我們帶著id給他,這邊以前用a-link,有兩種寫法

3-1-1、第一種設定法

第一種?=id(會有點難看懂,簡單就是說,用字串組成的方式,把id組合在?=後面)

> <a href="/wish_list_info?id=<%= wishlist.id %>"><%= @wishlist.title %></a>
 
> HTML印出下面這樣(假設我們今天按的連結是model的第一個實體,id會顯示為1)
> <a href="/wish_list_info?id=1"></a>

3-1-2、第二種設定法

第二種/id(這種事比較常見的)

> <a href="/wish_list_info/<%= wishlist.id %>"><%= @wishlist.title %></a>

> HTML印出下面這樣(假設我們今天按的連結是model的第一個實體,id會顯示為1)
> <a href="/wish_list_info/1"></a>

假設今天用a-link,也可以在後面帶其他字串給他(練習queryString)

> 我在id後面又加了?a=1&b=2這一串
> <a href="/wish_list_info/<%= wishlist.id %>?a=1&b=2"><%= @wishlist.title %></a>

> HTML印出下面這樣(假設我們今天按的連結是model的第一個實體,id會顯示為1)
> <a href="/wish_list_info/1?a=1&b=2"></a>

假設我們今天在controller,用params把剛剛a-link東西抓下來看看

> def show
>   render html: params
> end

可以發現剛剛顯示在網址上的東西,都被params抓成hash了

> 網址:localhost:3000/wish_list_info/1?a=1&b=2

> params下來的數據是下面,如果想要抓到特定的key,就用params[:a]就好
> {"id"=>"1", "a"=>"1", "b"=>"2", "controller"=>"wishlist", "action"=>"show"}

3-2、修改路徑

經過剛剛這麼長的研究,可以知道要查看每一筆詳細資料的話,因為要抓每一筆的id,所以路徑那邊要給一個變數id

> 原本路徑
> get "/wish_list_info", to: "wishlist#show"

> 帶入變數id的路徑
> get "/wish_list_info:id", to: "wishlist#show"

不過其實前面用a-link寫很醜,rails有支援link_to的寫法,可以直接用簡短的程式碼達成a-link在做的事

> <%= link_to @wishlist.title, wish_card_info_path(wishlist.id)  %>

4、SSR Vs. SPA

4-1、SSR = Server Side Rendering

wiki:一個web頁面的數據渲染完全由客戶端或者瀏覽器端來完成
常見於 rails

4-2、SPA = Single Page Application

wiki:是一種網路應用程式或網站的模型,它透過動態重寫當前頁面來與使用者互動,而非傳統的從伺服器重新載入整個新頁面。
常見於前端,React、vue


DoubleRenderError
遇到這個錯誤,代表你的render or redirect 在一個action 裡面,重複觸發兩次
只有把其中一個刪掉就可以繼續運行


5、終端機BUG問題 control+z、fg

Q. 如果今天進到rails + s -> 並使用control + z -> 接著又打rails s,然後整個畫面就卡住了
A. 解決方法為,使用fg(直接輸入fg,他就會把縮到背景的程式抓回來)

Ps1. control + z 會把目前執行的東西,縮到電腦背景(他其實還在跑)
Ps2. fg 意思是 fore ground => 可以把在背景執行的工作撿回來


面試題
Q. SQL injection(注入)是什麼
A. 也稱SQL隱碼或SQL注碼,是發生於應用程式與資料庫層的安全漏洞。
簡而言之,是在輸入的字串之中夾帶SQL指令,在設計不良的程式當中忽略了字元檢查,
那麼這些夾帶進去的惡意指令就會被資料庫伺服器誤認為是正常的SQL指令而執行,因此遭到破壞或是入侵。


6、script在html顯示為亂碼

Q. 為什麼rails中script的JS網址是一串亂碼??

A. 舊方法
因為瀏覽器很常會遇到靜態頁面暫存問題,但是你不能跟消費者說清一下暫存
因此早期要解決css/JS檔案暫存問題,使用queryString的方式,讓瀏覽者不會暫存此css檔案
queryString解決方法,就是幫每個檔案灌一個亂數,消費者每次開一次都會換一個數字
style.css?t=312312312
style.css?t=312312313
style.css?t=312312314
但是這個會造成,每次使用者進到網頁都會使用到網頁的暫存(多一串queryString)

A. 新方法
所以後來更改成,只要JS內部檔案只要改過,程式就會幫你算出一串雜湊值


如果今天用rails寫JS不會動,或是不如你預期,是為什麼,要記得是因為turbolinks的關係