2018/1/5

第十七天:怎麼讓別人連到我作好的網站?( 2018 iT邦幫忙鐵人賽-只要有心,人人都可以作卡米狗 )

markdown 你知道為什麼在瀏覽器輸入網址 [http://localhost:3000/](http://localhost:3000/) 就能連到自己的網頁伺服器嗎? # 認識 IP 位址(Internet Protocol Address) IP 位址就像經緯度,是由數字所構成。每一台電腦只要連上網,都會有一個 IP 位址。如果你想知道你的 IP 位址,可以開啟這個網頁:[http://www.whatismyip.com.tw/](http://www.whatismyip.com.tw/)。 舉個例:`127.0.0.1` 就是一個 IP 位址。 `127.0.0.1` 是一個特殊的 IP 位址,他代表的意義是`我家`。也就是說,任何人在瀏覽器上輸入 `127.0.0.1` 只會連到他自己的電腦。 所以輸入 [http://127.0.0.1:3000/](http://127.0.0.1:3000/),也能連到自己的網頁伺服器。 # 認識網址 一個網址包含很多資訊,讓我舉個例,以 [https://ithelp.ithome.com.tw/users/20107309/ironman/1253](https://ithelp.ithome.com.tw/users/20107309/ironman/1253) 來說: - `https://`:這是通訊協定,代表寄出的是明信片還是包裹 - `ithelp.ithome.com.tw`:這是網域,代表收件者住址 - `/users/20107309/ironman/1253`:這是路徑,代表收件者姓名 # 通訊協定 我們只討論 HTTP 和 HTTPS 通訊協定,之前也提到過很多次,這裡就不多講了。 # 網域(Domain Name) 因為 IP 位址不好記憶,就跟經緯度不好記憶一樣。一個有腦子的人類會幫每個地方取名,你也會想幫你的網站取名,那就是網域名稱,當然你取的名字必須是人家沒取過的。 每個網域會對應到一個 IP 位址,而 IP 位址對應到一台電腦,所以每個網域都會對應到一台電腦。如果你想要幫自己的電腦取名,你得找一個網域商購買你想要的網域名稱,還要指定這個網域所對應的 IP 位址。如果你很有錢的話,你也可以幫同一台電腦取多個名字。 因為 `localhost` 網域所對應的 IP位址就是 `127.0.0.1`,所以在瀏覽器輸入網址 [http://localhost:3000/](http://localhost:3000/) 就能連到自己的網頁伺服器。 # 路徑(Path) 在古代,每個路徑都會對應到一個檔案,古代的網址結尾還可以看見副檔名呢!現代的網頁伺服器都有路由功能,可以讀取路徑後決定要用哪一段程式來回應,也就是我們之前寫過的 Route 和 Controller Action。 # Port 一台電腦可以同時運作許多的網路服務,你可以想像成一家百貨公司有很多專櫃,要買不同的商品就要到不同的櫃位。如果你想買東西,你得知道你要去哪個櫃位買。而櫃位就是 Port。 啟動一個網頁伺服器,就像是雇一個櫃姐去站櫃台一樣,他必須一直站在那等客人來。就算都沒客人來,他也不能偷偷滑手機。 一台電腦可以同時啟動許多網頁伺服器,只要他們的網域或者 Port 不相同就可以。 如果網址沒有特別標示 Port,那就會採用預設值。預設的 HTTP Port 是 80,而 HTTPS Port 是 443。 舉個例:我們的網頁伺服器會去站在 Port 3000 的櫃位,所以我們連線到網頁伺服器要輸入[http://localhost:3000/](http://localhost:3000/) 或輸入 [http://127.0.0.1:3000/](http://127.0.0.1:3000/)。 # 怎麼讓別人連到我的網站? 剛才說 `localhost` 代表自己的電腦,所以你如果把這個網址傳給別人,別人點了之後不會連到你的電腦,而是連到他自己的電腦。所以如果我想要讓別人連到我的網站,我要給他怎樣的網址呢? 你需要給的網址在這裡:[http://www.whatismyip.com.tw/](http://www.whatismyip.com.tw/),這個網頁所告訴你的IP位址,是代表你電腦的IP位址,任何人輸入這個IP位址都是連到你這。 假設我的IP位址是 `100.100.100.100`,那麼在網址列輸入 [http://100.100.100.100:3000/](http://100.100.100.100:3000/) 就會連到我的網站。 # 可是我剛剛試了但沒有成功 事情並沒有這麼單純。如果你試了但沒有成功,表示你的電腦是透過防火牆連上網路,剛剛那個網頁所取得的 IP 位址是防火牆的 IP 位址,不是你的電腦的 IP 位址,所以人家連不進你的電腦。 你必須在防火牆設定當有人要從 Port 3000 連到防火牆時,就把連線轉到你的電腦上,這樣人家才連得到你的電腦。我不打算教你怎麼設定防火牆,因為每個人的網路環境不同,而且你的電腦應該也不會想一直開著。我試過了,開著一個月都沒關的電費大概是 600 元。 # 所以要怎麼讓別人連到我的網站? 我們在開發網站的時候,通常只有在測試網頁功能時會開啟網頁伺服器。像這種不給外人連線的環境,稱為開發環境。而我們會建立另一個專門給外人連線的環境,稱為發布環境。 當一個網站寫好之後,我們會把程式碼放到一台不關機的主機上,這台主機需要設定網域、網路環境,需要安裝網頁伺服器相關程式,然後啟動網頁伺服器就放著。建立一個發布環境也需要很多知識,根據設備不同,要作的事情也會不同。 # 那有比較簡單的方法嗎? 有的,如果我們使用別人家的發布環境,那我們只需要把我們的程式碼放到別人的伺服器上就搞定了。 以卡米狗為例,我是使用 Heroku 來發布卡米狗,Heroku 有一定的免費額度,如果用量小的話是不用花錢的。我一開始也是這樣想,所以就把卡米狗放上去了,但他已經變成一個吃貨。 如果你想要使用 Heroku,你需要在 Heroku 註冊一個帳號,然後再把自己的程式碼傳上去,上傳程式碼需要使用 git 這個軟體。 git 是一個版本控管軟體,你可以直接理解為他是用來上傳和下載程式碼的軟體就好,不用太在意什麼版本控管,接下來我只會講怎麼使用 git 上傳程式碼,不會提到其他部分。有興趣深入學習 git 的人可以參考這個電子書:[https://gitbook.tw/](https://gitbook.tw/),如果覺得習慣看影片學的人可以走這邊:[https://campus.5xruby.tw/p/git](https://campus.5xruby.tw/p/git)。 明天會講怎麼把我們作好的廢物網站弄上 Heroku。

2018/1/4

第十六天:做一個最簡單的爬蟲( 2018 iT邦幫忙鐵人賽-只要有心,人人都可以作卡米狗 )

markdown 一直以來,我們都是用瀏覽器發出 `HTTP request`,打到我們的 Rails Server,然後我們的 Rails Server 再傳回 `HTTP Response`。 我們還沒有試過用 Rails Server 發出 `HTTP request`,今天就來試一下。 # 用 Rails 發 HTTP request ### Routes 在 `config/routes.rb` 加入 ``` get '/kamigo/sent_request', to: 'kamigo#sent_request' ``` ### Action 在 `app/controllers/kamigo_controller.rb` 加入 ``` def sent_request uri = URI('http://localhost:3000/kamigo/response_body') response = Net::HTTP.get(uri) render plain: response end ``` 這是最簡單能發出 `Request` 的方法,我們先把網址字串轉換成 `URI` 物件,透過 `Net::HTTP.get` 這個方法,他會接受一個網址,然後把 `Response` 的 `Body` 部分以字串的形式傳回。在這段程式裡面我們只能看到傳入網址跟最終結果,我們不能調整 `Request Header` 和 `Request Body`,也不能觀察 `Response Header`。 我讓他連去 [http://localhost:3000/kamigo/response_body](http://localhost:3000/kamigo/response_body),這是我們昨天作好的東西。 ### 在瀏覽器開啟網址 網址:[http://localhost:3000/kamigo/sent_request](http://localhost:3000/kamigo/sent_request) 開啟網址後會看到: ``` 虎哇花哈哈哈 ``` ### 小黑框的顯示結果 ``` Started GET "/kamigo/sent_request" for 127.0.0.1 at 2018-01-04 01:06:38 +0800 Processing by KamigoController#sent_request as HTML Started GET "/kamigo/response_body" for 127.0.0.1 at 2018-01-04 01:06:39 +0800 Processing by KamigoController#show_response_body as */* ===這是設定前的response.body:=== Rendering text template Rendered text template (0.0ms) ===這是設定後的response.body:虎哇花哈哈哈=== Completed 200 OK in 344ms (Views: 9.2ms) Rendering text template Rendered text template (0.0ms) Completed 200 OK in 1400ms (Views: 2.7ms) ``` 突然想到有個東西還沒講,那就是 Ruby 的函數。 # Ruby 的函數 一個函數可以把一個東西變成另一個東西,或者把多個東西變成另一個東西。 舉個例:我們來作一個函數,輸入是一句話,我們要把這句話變成韓文。 ### 函數宣告 ``` def translate_to_korean(message) "#{message}油~" end ``` 一個函數裡面可以寫一段程式,最後一行程式的執行結果就會是函數的傳回值。 不管傳入什麼,我們就在後面加上`油~`。 ### 函數呼叫 ``` translate_to_korean('愛老虎') ``` 你想要使用小括號或者使用空白框住傳入的參數都可以。 ``` translate_to_korean '愛老虎' ``` ### 結果 ``` "愛老虎油~" ``` 成功翻譯成韓文! # 把翻譯韓文的功能加入到上面的爬蟲 ### 修改 Action ``` def sent_request uri = URI('http://localhost:3000/kamigo/response_body') response = Net::HTTP.get(uri) render plain: translate_to_korean(response) end ``` ### 開啟網頁 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9MdSRnLh1llHSA6vjV0L2PIpk2vHDkN24yQojeqoZoi_o9qVpC4FweKUukoDZxTis3cZLZXR7-XI0LssKlU1a_AW-M0_7dSGpF-iqzqS_rlG2Cc43ATtI14Yp2aApZwDgD3TrbA4jZBs/s1600/1.jpg) 壞掉囉,錯誤訊息是 `incompatible character encodings: ASCII-8BIT and UTF-8`。 下面那個深灰色區域是給我們除錯用的,有點類似 `irb`,我們可以在下面輸入一點東西: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiubOD2gKe_ypBCjGrsay6W5JLpo946U0rvcn0lVbGndI6XsXh9et3HGSJwMbVqfAjC0py6bJbl-ypO2X-THDfllUyfl-ydpwJeuDeoy4RF5oZ-PPIGXaMroXGvz1nP9cpN1KCviL6jzIk/s1600/2.jpg) 我輸入了 `message`,看看 message 的值是什麼,結果看到是這樣: ``` >> message => "\xE8\x99\x8E\xE5\x93\x87\xE8\x8A\xB1\xE5\x93\x88\xE5\x93\x88\xE5\x93\x88" >> ``` 因為我們獲得的 response 字串的編碼是 `ASCII-8BIT`,沒辦法直接跟 `UTF-8` 編碼的 "油~" 加在一起。 所以修正的方法就是把 response 字串轉碼為 UTF-8 即可: ### 再次修改 Action ``` def sent_request uri = URI('http://localhost:3000/kamigo/response_body') response = Net::HTTP.get(uri).force_encoding("UTF-8") render plain: translate_to_korean(response) end ``` 在字串後面寫 `.force_encoding("UTF-8")` 就可以把編碼轉為 `UTF-8` 格式了。 ### 再次開啟網頁 ``` 虎哇花哈哈哈油~ ``` 玩樂時間結束,來作點正事,目前的寫法沒辦法觀察 `request` 和 `response`,我們需要能觀察 `request` 和 `response` 的寫法。 # 能觀察 request 和 response 的寫法 ### 修改 Action ``` def sent_request uri = URI('http://localhost:3000/kamigo/eat') http = Net::HTTP.new(uri.host, uri.port) http_request = Net::HTTP::Get.new(uri) http_response = http.request(http_request) render plain: JSON.pretty_generate({ request_class: request.class, response_class: response.class, http_request_class: http_request.class, http_response_class: http_response.class }) end ``` 越來越複雜了。通常要作到細膩的控制,就要寫比較多的程式碼。 我們先觀察一下這四個物件:`request`、`response`、`http_request`、`http_response`,前兩個是 Rails 內建的,後兩個是我們在 Action 中定義的,為了能夠同時輸出 4 個字串,我用一個雜湊陣列把想看的字串都放進去,然後使用 `JSON.pretty_generate` 幫我排成比較好閱讀的格式,讓我們來看一下個別對應的類別: ### 開啟網頁 ``` { "request_class": "ActionDispatch::Request", "response_class": "ActionDispatch::Response", "http_request_class": "Net::HTTP::Get", "http_response_class": "Net::HTTPOK" } ``` ### 圖解 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4WpA9VIeFxBdMV5WVTmXAlmBdgq_Y8xE2cBurbze0HcbV5cb3wsgD0mNjA8ZskFBAgyOIaogEUqjJv_eeM8nQ_ic9wSx1aG1LjfyC-s65rz-qyG7tlSn3s0_t-SoKjNRuQ3BlDLTG1KA/s1600/3.jpg) 昨天我們學的是 `1` 跟 `4`,今天學 `2` 跟 `3`,這裡的四個物件,都是屬於不同的類別,所以我們可能需要不同的語法去存取他們。 今天只要了解到這裡就行了。 # 總結 - 不是只有瀏覽器才能發出 HTTP Request,網頁伺服器一樣可以發出 HTTP Request - 今天學會了宣告函數和呼叫函數的正確觀念。 明天講怎麼讓別人能連到你作的網站。

2018/1/3

第十五天:從 Rails 認識 HTTP 協定( 2018 iT邦幫忙鐵人賽-只要有心,人人都可以作卡米狗 )

markdown 昨天我們學會了怎麼新增 Route 跟 Controller,並且知道怎麼控制要回應什麼內容給瀏覽器。 今天要使用這兩天學會的東西,我們要來看看瀏覽器到底傳了什麼給 Rails,以及 Rails 傳回什麼給瀏覽器。 # 做一組新的 Route 跟 Controller 我們在 `app/controllers/kamigo_controller.rb` 新增一個 `request_headers` 方法,嘗試把 `request` 中的 `headers` 傳回給瀏覽器。 ``` class KamigoController < ApplicationController def eat render plain: "吃土啦" end def request_headers render plain: request.headers end end ``` 然後在 `config/routes.rb` 加入一行 `get '/kamigo/request_headers', to: 'kamigo#request_headers'`。 ``` Rails.application.routes.draw do get '/kamigo/eat', to: 'kamigo#eat' get '/kamigo/request_headers', to: 'kamigo#request_headers' end ``` 開啟網頁伺服器,並且用瀏覽器打開網頁:[http://localhost:3000/kamigo/request_headers](http://localhost:3000/kamigo/request_headers)。 你會看到: ``` # ``` 表示 `request.headers` 是一個 [ActionDispatch::Http::Headers](http://api.rubyonrails.org/classes/ActionDispatch/Http/Headers.html) 物件,但是其實我們想知道的是他包含的內容是什麼,而不是他是什麼類別的物件。 # 從 Rails 觀察 request.headers 所以我們需要改一下程式: ``` class KamigoController < ApplicationController def eat render plain: "吃土啦" end def request_headers render plain: request.headers.to_h end end ``` 加一個 `to_h` ,試著把物件轉成雜湊陣列看看。 ``` {"rack.version"=>[1, 3],... 下略 180 萬字 ``` 結果是成功了,但是瀏覽器印出了 180 萬個字。 我們先試著忽略內容的部分,看一下他的關鍵字有哪些。 ``` class KamigoController < ApplicationController def eat render plain: "吃土啦" end def request_headers render plain: request.headers.to_h.keys end end ``` 再加一個 `keys` ,試著把雜湊陣列轉成只包含關鍵字的陣列看看。 ``` ["rack.version", "rack.errors", "rack.multithread", "rack.multiprocess", "rack.run_once", "SCRIPT_NAME", "QUERY_STRING", "SERVER_PROTOCOL", "SERVER_SOFTWARE", "GATEWAY_INTERFACE", "REQUEST_METHOD", "REQUEST_PATH", "REQUEST_URI", "HTTP_VERSION", "HTTP_HOST", "HTTP_CONNECTION", "HTTP_CACHE_CONTROL", "HTTP_USER_AGENT", "HTTP_UPGRADE_INSECURE_REQUESTS", "HTTP_ACCEPT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "HTTP_COOKIE", "HTTP_ALEXATOOLBAR_ALX_NS_PH", "HTTP_IF_NONE_MATCH", "SERVER_NAME", "SERVER_PORT", "PATH_INFO", "REMOTE_ADDR", "puma.socket", "rack.hijack?", "rack.hijack", "rack.input", "rack.url_scheme", "rack.after_reply", "puma.config", "action_dispatch.parameter_filter", "action_dispatch.redirect_filter", "action_dispatch.secret_token", "action_dispatch.secret_key_base", "action_dispatch.show_exceptions", "action_dispatch.show_detailed_exceptions", "action_dispatch.logger", "action_dispatch.backtrace_cleaner", "action_dispatch.key_generator", "action_dispatch.http_auth_salt", "action_dispatch.signed_cookie_salt", "action_dispatch.encrypted_cookie_salt", "action_dispatch.encrypted_signed_cookie_salt", "action_dispatch.cookies_serializer", "action_dispatch.cookies_digest", "action_dispatch.routes", "ROUTES_57612100_SCRIPT_NAME", "ORIGINAL_FULLPATH", "ORIGINAL_SCRIPT_NAME", "action_dispatch.request_id", "action_dispatch.remote_ip", "rack.session", "rack.session.options", "action_dispatch.request.path_parameters", "action_controller.instance", "action_dispatch.request.content_type", "action_dispatch.request.request_parameters", "rack.request.query_string", "rack.request.query_hash", "action_dispatch.request.query_parameters", "action_dispatch.request.parameters", "action_dispatch.request.formats", "rack.request.cookie_hash", "rack.request.cookie_string", "action_dispatch.cookies", "action_dispatch.request.unsigned_session_cookie"] ``` 看起來好多了,但是這不好閱讀,我們試著讓他自己換行。 ``` class KamigoController < ApplicationController def eat render plain: "吃土啦" end def request_headers render plain: request.headers.to_h.keys.join("\n") end end ``` 再加一個 `join("\n")`,試著把陣列轉成字串,並且以 `\n` 作為分隔符號,`\n` 其實是換行的意思。 ``` rack.version rack.errors rack.multithread rack.multiprocess rack.run_once SCRIPT_NAME QUERY_STRING SERVER_PROTOCOL SERVER_SOFTWARE GATEWAY_INTERFACE REQUEST_METHOD REQUEST_PATH REQUEST_URI HTTP_VERSION HTTP_HOST HTTP_CONNECTION HTTP_CACHE_CONTROL HTTP_USER_AGENT HTTP_UPGRADE_INSECURE_REQUESTS HTTP_ACCEPT HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_COOKIE HTTP_ALEXATOOLBAR_ALX_NS_PH HTTP_IF_NONE_MATCH SERVER_NAME SERVER_PORT PATH_INFO REMOTE_ADDR puma.socket rack.hijack? rack.hijack rack.input rack.url_scheme rack.after_reply puma.config action_dispatch.parameter_filter action_dispatch.redirect_filter action_dispatch.secret_token action_dispatch.secret_key_base action_dispatch.show_exceptions action_dispatch.show_detailed_exceptions action_dispatch.logger action_dispatch.backtrace_cleaner action_dispatch.key_generator action_dispatch.http_auth_salt action_dispatch.signed_cookie_salt action_dispatch.encrypted_cookie_salt action_dispatch.encrypted_signed_cookie_salt action_dispatch.cookies_serializer action_dispatch.cookies_digest action_dispatch.routes ROUTES_57612100_SCRIPT_NAME ORIGINAL_FULLPATH ORIGINAL_SCRIPT_NAME action_dispatch.request_id action_dispatch.remote_ip rack.session rack.session.options action_dispatch.request.path_parameters action_controller.instance action_dispatch.request.content_type action_dispatch.request.request_parameters rack.request.query_string rack.request.query_hash action_dispatch.request.query_parameters action_dispatch.request.parameters action_dispatch.request.formats rack.request.cookie_hash rack.request.cookie_string action_dispatch.cookies action_dispatch.request.unsigned_session_cookie ``` 我們需要過濾掉不想看的東西,但是現在還是沒有很容易閱讀,我們應該排序一下。 ``` def request_headers render plain: request.headers.to_h.keys.sort.join("\n") end ``` 再加一個 `sort`,試著在轉完陣列後,先排序,再作排版文字。 ``` GATEWAY_INTERFACE HTTP_ACCEPT HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_ALEXATOOLBAR_ALX_NS_PH HTTP_CACHE_CONTROL HTTP_CONNECTION HTTP_COOKIE HTTP_HOST HTTP_IF_NONE_MATCH HTTP_UPGRADE_INSECURE_REQUESTS HTTP_USER_AGENT HTTP_VERSION ORIGINAL_FULLPATH ORIGINAL_SCRIPT_NAME PATH_INFO QUERY_STRING REMOTE_ADDR REQUEST_METHOD REQUEST_PATH REQUEST_URI ROUTES_57612100_SCRIPT_NAME SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE action_controller.instance action_dispatch.backtrace_cleaner action_dispatch.cookies action_dispatch.cookies_digest action_dispatch.cookies_serializer action_dispatch.encrypted_cookie_salt action_dispatch.encrypted_signed_cookie_salt action_dispatch.http_auth_salt action_dispatch.key_generator action_dispatch.logger action_dispatch.parameter_filter action_dispatch.redirect_filter action_dispatch.remote_ip action_dispatch.request.content_type action_dispatch.request.formats action_dispatch.request.parameters action_dispatch.request.path_parameters action_dispatch.request.query_parameters action_dispatch.request.request_parameters action_dispatch.request.unsigned_session_cookie action_dispatch.request_id action_dispatch.routes action_dispatch.secret_key_base action_dispatch.secret_token action_dispatch.show_detailed_exceptions action_dispatch.show_exceptions action_dispatch.signed_cookie_salt puma.config puma.socket rack.after_reply rack.errors rack.hijack rack.hijack? rack.input rack.multiprocess rack.multithread rack.request.cookie_hash rack.request.cookie_string rack.request.query_hash rack.request.query_string rack.run_once rack.session rack.session.options rack.url_scheme rack.version ``` 我們需要觀察一下每一個 key 裡面包含的內容,來決定哪些是我們不要的。 ``` def request_headers render plain: request.headers.to_h.map{ |key, value| key + ": " + value.class.to_s }.sort.join("\n") end ``` 我們把 `keys` 改成了 `.map{ |key, value| key + ": " + value.class.to_s}`,map 的意思是把每一筆紀錄都拿去作一個轉換,然後傳回一個陣列。 如果我們是寫成 `.map{ |key, value| key }` 的話,就是每一筆紀錄我們只要保留關鍵字的部分,這樣就等於 `keys`,這兩段程式會有相同的結果。 但是因為我們想要看到更多的東西,所以我們再把 `key` 改寫成 `key + ": " + value.class.to_s`,我希望在每一個 key 後面加一個冒號,然後讓 Rails 告訴我,他們是什麼類型的資料。 得到的結果是這樣: ``` GATEWAY_INTERFACE: String HTTP_ACCEPT: String HTTP_ACCEPT_ENCODING: String HTTP_ACCEPT_LANGUAGE: String HTTP_ALEXATOOLBAR_ALX_NS_PH: String HTTP_CACHE_CONTROL: String HTTP_CONNECTION: String HTTP_COOKIE: String HTTP_HOST: String HTTP_UPGRADE_INSECURE_REQUESTS: String HTTP_USER_AGENT: String HTTP_VERSION: String ORIGINAL_FULLPATH: String ORIGINAL_SCRIPT_NAME: String PATH_INFO: String QUERY_STRING: String REMOTE_ADDR: String REQUEST_METHOD: String REQUEST_PATH: String REQUEST_URI: String ROUTES_57612100_SCRIPT_NAME: String SCRIPT_NAME: String SERVER_NAME: String SERVER_PORT: String SERVER_PROTOCOL: String SERVER_SOFTWARE: String action_controller.instance: KamigoController action_dispatch.backtrace_cleaner: Rails::BacktraceCleaner action_dispatch.cookies: ActionDispatch::Cookies::CookieJar action_dispatch.cookies_digest: NilClass action_dispatch.cookies_serializer: Symbol action_dispatch.encrypted_cookie_salt: String action_dispatch.encrypted_signed_cookie_salt: String action_dispatch.http_auth_salt: String action_dispatch.key_generator: ActiveSupport::CachingKeyGenerator action_dispatch.logger: ActiveSupport::Logger action_dispatch.parameter_filter: Array action_dispatch.redirect_filter: Array action_dispatch.remote_ip: ActionDispatch::RemoteIp::GetIp action_dispatch.request.content_type: NilClass action_dispatch.request.formats: Array action_dispatch.request.parameters: ActiveSupport::HashWithIndifferentAccess action_dispatch.request.path_parameters: Hash action_dispatch.request.query_parameters: ActiveSupport::HashWithIndifferentAccess action_dispatch.request.request_parameters: ActiveSupport::HashWithIndifferentAccess action_dispatch.request.unsigned_session_cookie: Hash action_dispatch.request_id: String action_dispatch.routes: ActionDispatch::Routing::RouteSet action_dispatch.secret_key_base: String action_dispatch.secret_token: NilClass action_dispatch.show_detailed_exceptions: TrueClass action_dispatch.show_exceptions: TrueClass action_dispatch.signed_cookie_salt: String puma.config: Puma::Configuration puma.socket: TCPSocket rack.after_reply: Array rack.errors: IO rack.hijack: Puma::Client rack.hijack?: TrueClass rack.input: Puma::NullIO rack.multiprocess: FalseClass rack.multithread: TrueClass rack.request.cookie_hash: Hash rack.request.cookie_string: String rack.request.query_hash: Hash rack.request.query_string: String rack.run_once: FalseClass rack.session.options: ActionDispatch::Request::Session::Options rack.session: ActionDispatch::Request::Session rack.url_scheme: String rack.version: Array ``` 讓我們簡化一下程式碼 ``` def request_headers render plain: request.headers.to_h.map{ |key, value| "#{key}: #{value.class}" }.sort.join("\n") end ``` `"#{key}: #{value.class}"` 是更加簡潔的表示法,用雙引號包裝的字串可以作變數的代換,語法是 `#{變數的名字}`,這種寫法的程式碼會更好閱讀。 看起來那些 `key` 包含 `action_controller`、`action_dispatch`、`puma`、`rack` 的資料都是我們不想看到的,因為他們看起來像是 Rails 這邊才產生的資料,而不是瀏覽器傳來的資料。 我發現這些我們不想要的 key 都包含 `.`,讓我們把在 `key` 包含 `.` 的資料都過濾掉。 ``` def request_headers render plain: request.headers.to_h.reject{ |key, value| key.include? '.' }.map{ |key, value| "#{key}: #{value.class}" }.sort.join("\n") end ``` reject 可以過濾資料,滿足條件 `key.include? '.'` 的資料就沒有辦法進到 map。 得到的結果是這樣: ``` GATEWAY_INTERFACE: String HTTP_ACCEPT: String HTTP_ACCEPT_ENCODING: String HTTP_ACCEPT_LANGUAGE: String HTTP_ALEXATOOLBAR_ALX_NS_PH: String HTTP_CACHE_CONTROL: String HTTP_CONNECTION: String HTTP_COOKIE: String HTTP_HOST: String HTTP_UPGRADE_INSECURE_REQUESTS: String HTTP_USER_AGENT: String HTTP_VERSION: String ORIGINAL_FULLPATH: String ORIGINAL_SCRIPT_NAME: String PATH_INFO: String QUERY_STRING: String REMOTE_ADDR: String REQUEST_METHOD: String REQUEST_PATH: String REQUEST_URI: String ROUTES_57612100_SCRIPT_NAME: String SCRIPT_NAME: String SERVER_NAME: String SERVER_PORT: String SERVER_PROTOCOL: String SERVER_SOFTWARE: String ``` 現在應該可以試試看從顯示資料的類別改成顯示完整資料了。 ``` def request_headers render plain: request.headers.to_h.reject{ |key, value| key.include? '.' }.map{ |key, value| "#{key}: #{value}" }.sort.join("\n") end ``` 看起來像這樣: ``` GATEWAY_INTERFACE: CGI/1.2 HTTP_ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 HTTP_ACCEPT_ENCODING: gzip, deflate, br HTTP_ACCEPT_LANGUAGE: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6 HTTP_ALEXATOOLBAR_ALX_NS_PH: AlexaToolbar/alx-4.0 HTTP_CACHE_CONTROL: max-age=0 HTTP_CONNECTION: keep-alive HTTP_COOKIE: _blog_session=RG5tTnJ2eUIyMkFCUFV1UHFaMDhHbXpYekJXbFhQQlBiRlk1UEhucUkvV0IwcDNtV09sNUJuVTJWOS9RU2cweTgxY0Q1TGQ5L21GRWNmS1Z6RmdEa3cxdmxFUnZPOEJObVN3ck10R3Frc2ZOWXBDT2hNY0VZUUg0RHowNlJuazFXeWJXZE5sNjlsTUFBMG12QVJNRmVnPT0tLXo3dEJjUFJtbzRjSy9HODVNcVJRRVE9PQ%3D%3D--02e477b161398d92622542cb43d8b515892b8c59; _ironman_session=SzRPTlE1VytFbFgwRUkxMHlXaDFUVlpqdzN4ekt1eVFtUFpBbmtxNUw0UFd6cytjNkI1MlBOdC9zcnBkejNUMmxLa1MzdnV6Z0dselhHOTEzSUtJTDlrU241empqS1BmanNHSTBLd1VnVzlHV2pDZis4QVVRUk5xandJaGpPNml5VHlHUTI5ZXNCa1NZdHNrNzBYT0lnPT0tLW5salNpWUNVRmlxbUI1U1ZLdXhwMGc9PQ%3D%3D--c56c1473a20c57c62c66a215e07fa14dd6f7fb94 HTTP_HOST: localhost:3000 HTTP_IF_NONE_MATCH: W/"d220fb78d8ab10894ccdd2ef500f5ac8" HTTP_UPGRADE_INSECURE_REQUESTS: 1 HTTP_USER_AGENT: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 HTTP_VERSION: HTTP/1.1 ORIGINAL_FULLPATH: /kamigo/request_headers ORIGINAL_SCRIPT_NAME: PATH_INFO: /kamigo/request_headers QUERY_STRING: REMOTE_ADDR: 127.0.0.1 REQUEST_METHOD: GET REQUEST_PATH: /kamigo/request_headers REQUEST_URI: /kamigo/request_headers ROUTES_57612100_SCRIPT_NAME: SCRIPT_NAME: SERVER_NAME: localhost SERVER_PORT: 3000 SERVER_PROTOCOL: HTTP/1.1 SERVER_SOFTWARE: puma 3.11.0 Love Song ``` 這應該就是從瀏覽器那邊傳來的全部內容了,我們可以用瀏覽器的 `F12` 看一下差異。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVLQmK5-8hobOxHwM7fDPKwozabp4QBB9nD5y1M7uWUnAaoTC26qgdazr63oKr1QUt9wkA-fiJDXi8o0RsPvhj3fUCCoFsre9sLFP2mq84JNUx93Jx-yWeLfeUmbQ06-LUZF3Elf0CdVs/s1600/1.jpg) 因為這樣我們無法同時看到兩邊的結果,所以需要調整一下開發人員工具的位置,把他調到右側的話會比較恰當。 先點一下這裡: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi17I9Fn8peVszVuVCgrMpIzaXYW81_aPzQLwIxaecUaWtn9I1ktlyTrvsPUR7WlEhlT9PPzrgeEa45C_bX4L-rTUGbh-hg6orDO-IR0Ih_92jaqiQqovyUOo9wHhZ8HKe-_KaLlpWxZOo/s1600/2.jpg) 再點一下這裡: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSl6Rh2CGVY5PgklfbthWbO1HEtakh-mo6EmmpD61LYt-lO8LknqHYh8rLmA0USMTb-tQx7RMFrvOoduJkiAsFNizUiYprtxxviQq-L46dpZPk8OIL6lInWX0jgA0CgYhUui_pHrpa2rg/s1600/3.jpg) 點完之後應該會變成這樣: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgodGMjV9ohQIhJfHwtTraSHzSa5huE3sDYkq3iwLYrxFRooCd0I30fyBoXDtoKLEo6LVYq9_w6oE9Locc26T1JG414bQGzDhwjzMRxtnSSMMgkm9SShxDA-lUSzaSPOwmV3t7hO7Ycp6Y/s1600/4.jpg) 仔細一看,發現這兩塊是一樣的: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_HKxX1Svx8CCBpNTgJnmav1jkJ71BN3c7xdOWaIKIUHHWKc0s07lZl8wvIFL2Aip8tSF2t0G-y3CDfWInp9uTUGJvQsvKtgWuo9X18ycAyRDIKcdIMukDnsQpibHVZ52KVbgVOL6Zscw/s1600/5.jpg) # 從 Rails 觀察 request.body 再作一組 Route 和 Controller Action。 Route: ``` get '/kamigo/request_body', to: 'kamigo#request_body' ``` Action: ``` def request_body render plain: request.body end ``` 用瀏覽器連過去看:[http://localhost:3000/kamigo/request_body](http://localhost:3000/kamigo/request_body),發現是 `#`,表示瀏覽器傳了一個空的 request body 過來,因為他是來下載網頁,不是上傳資料。如果是上傳資料的話,就會在 request body 有東西了。 # 從 Rails 觀察 response.headers 再作一組 Route 和 Controller Action。 Route: ``` get '/kamigo/response_headers', to: 'kamigo#response_headers' ``` Action: ``` def response_headers render plain: response.headers.to_h.map{ |key, value| "#{key}: #{value}" }.sort.join("\n") end ``` 用瀏覽器連過去看:[http://localhost:3000/kamigo/response_headers](http://localhost:3000/kamigo/response_headers)。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHNFQ0c4JSoIiOcWfbd23S1W32PgGer8tRw5LxDgPYe588Q_49amNbEyTZNzAv5fgyMLEtSNu3SheQSAyGrckphvd-SWOZ708uFxgQKr3LMDe98HuLyJ9HEFUF-x9Q956Uy45SJWWMI4g/s1600/6.jpg) 資料少很多,這表示 Rails 回傳的東西,不是全放在 `response.headers`,不過這不是很重要,知道有 `response` 這個東西就行了。 我們可以試著加一個 header 看看: ``` def response_headers response.headers['5566'] = 'QQ' render plain: response.headers.to_h.map{ |key, value| "#{key}: #{value}" }.sort.join("\n") end ``` ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW05FXWJNo1QOU3mDjAEhbHTKH97KppDbmgNZwglXEBiAKftpJiOf_5cUTmtYOpMj7khUSAofEvxRrg9sjM2pHsYzD8V5utjztugCaofwIyxPiaEsnNTK3FiMacbwYGf8mY5YRK2SuTvQ/s1600/7.jpg) 哈哈哈哈ㄚ哈 # 從 Rails 觀察 response.body 再作一組 Route 和 Controller Action。 Route: ``` get '/kamigo/response_body', to: 'kamigo#show_response_body' ``` Action: ``` def show_response_body render plain: response.body end ``` 這裡我們不能用 `response_body` ,因為 `response_body` 已經被 Rails 用掉了,我們再用的話會出事,所以這邊稍微改一下名字。 用瀏覽器連過去看:[http://localhost:3000/kamigo/response_body](http://localhost:3000/kamigo/response_body)。 這樣會看到空的結果,因為我們傳回的內容就是 `response.body`,但此時的 `response.body` 其實還是空值。 所以我們必須改變一下觀察的方法,其實我們可以透過小黑框來觀察 Rails 的目前狀況。 在程式碼當中加入 `puts "媽我在這 \\( ̄▽ ̄)/"` 意思是把 `媽我在這 \\( ̄▽ ̄)/` 顯示在小黑框上。 ``` def show_response_body puts "媽我在這 \\( ̄▽ ̄)/" render plain: response.body end ``` 當有人開啟網頁 [http://localhost:3000/kamigo/response_body](http://localhost:3000/kamigo/response_body),就會在小黑框看到這個結果: ``` Started GET "/kamigo/response_body" for 127.0.0.1 at 2018-01-03 03:46:48 +0800 Processing by KamigoController#show_response_body as HTML 媽我在這 \( ̄▽ ̄)/ Rendering text template Rendered text template (0.0ms) Completed 200 OK in 2ms (Views: 0.8ms) ``` 所以我們利用這種方式觀察,現在把程式改成這樣: ``` def show_response_body puts "===這是設定前的response.body:#{response.body}===" render plain: "虎哇花哈哈哈" puts "===這是設定後的response.body:#{response.body}===" end ``` 當有人開啟網頁 [http://localhost:3000/kamigo/response_body](http://localhost:3000/kamigo/response_body),就會在小黑框看到這個結果: ``` Started GET "/kamigo/response_body" for 127.0.0.1 at 2018-01-03 03:51:40 +0800 Processing by KamigoController#show_response_body as HTML ===這是設定前的response.body:=== Rendering text template Rendered text template (0.0ms) ===這是設定後的response.body:虎哇花哈哈哈=== Completed 200 OK in 16ms (Views: 11.5ms) ``` # 總結 那個要抄程式碼的,都幫你整理好了。 ### app/controllers/kamigo_controller.rb ``` class KamigoController < ApplicationController def eat render plain: "吃土啦" end def request_headers render plain: request.headers.to_h.reject{ |key, value| key.include? '.' }.map{ |key, value| "#{key}: #{value}" }.sort.join("\n") end def response_headers response.headers['5566'] = 'QQ' render plain: response.headers.to_h.map{ |key, value| "#{key}: #{value}" }.sort.join("\n") end def request_body render plain: request.body end def show_response_body puts "===這是設定前的response.body:#{response.body}===" render plain: "虎哇花哈哈哈" puts "===這是設定後的response.body:#{response.body}===" end end ``` ### config/routes.rb ``` Rails.application.routes.draw do get '/kamigo/eat', to: 'kamigo#eat' get '/kamigo/request_headers', to: 'kamigo#request_headers' get '/kamigo/request_body', to: 'kamigo#request_body' get '/kamigo/response_headers', to: 'kamigo#response_headers' get '/kamigo/response_body', to: 'kamigo#show_response_body' end ``` ### 網址 [http://localhost:3000/kamigo/request_headers](http://localhost:3000/kamigo/request_headers) [http://localhost:3000/kamigo/request_body](http://localhost:3000/kamigo/request_body) [http://localhost:3000/kamigo/response_headers](http://localhost:3000/kamigo/response_headers) [http://localhost:3000/kamigo/response_body](http://localhost:3000/kamigo/response_body) 今天就講到這裡。 嗯?今天是不是講太多了?

2018/1/2

第十四天:最基本的 Rails 運作流程( 2018 iT邦幫忙鐵人賽-只要有心,人人都可以作卡米狗 )

markdown 這是目前我們理解的 HTTP 協定。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDDd1WmueuUPejQ2gCRQbVDSFbInhKL0oyAmw7sNqyig12R8zvki2y6erh0SWsyOjik9de9uR51K80kpdtT0fNOlP5zjoCBn_5Oggl4Cg74MLvclMcd24E2AvKj1EfiuzQxkSIWODcDcY/s1600/1.jpg) 今天會詳細說明 Rails 是怎麼處理一個 HTTP Request。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4g95h3LW5Mx3WOdL0uJ-WN9GPCgAz2VV7L7ZzHPgf4zai5c37xlMsWqYvef7lH2ye7QWxFkIzdGd8hlVpPYi3zzCHUCWHHRt9X0ZfpaxdsxJHC8V-dH09xgvluzFhB2rZahNoz1zeOQg/s1600/2.jpg) 當我們在瀏覽器輸入一個網址時,會發出一個 Get Request 到網頁伺服器。網頁伺服器會根據這個網址,決定他應該要幹嘛。而 Rails 中的 Route 負責分析網址, Route 會決定要把 Request 發給哪一個 Controller 的 Action 來做回應。因為 Action 畫上去圖會很亂,所以就乾脆不畫了。 應該很難懂吧? 用人話來說:Route 就像大樓警衛,當有信件送進來,他要負責把信交給對應的住戶,所以 Controller 就是住家,Action 就是裡面的人。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiJvFOxKgtGVh-butNSV3OF4VjZ0pVUM0Y_NIIZMIcMZZdCipS2KWDYXgM6O2nehpsiuX39wV5hhyphenhypheneqpqKCthCdGqv1IGa26QETANR-7ywVmNczv9pCl8JJOu7AvNdI6MHWhqspWIlLQA/s1600/3.jpg) 當然,一棟大樓會有一間警衛室,以及有很多住戶,而且每一戶裡面還有住很多人,但是一封信只會寄給一個人。 # 所以我說那個警衛室在哪? 先打開 sublime text,然後點左上角的 `File`(檔案)。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio98LWPjpMfPCkWhOHKuV_ggbon44B0ALKpgZrQiyC6Nx3og4DS6WoVNSUBZZmZHgSGfUR3bVyHXGdo84mvyJu9dv_VM1yUVqtAdUahrKm0ll3mnG_g57vgJfgAZ0-ZyJYqO2oinLfTbA/s1600/4.jpg) 選擇 `Open Folder...`(打開資料夾),選到我們的專案資料夾:`D:\只要有心,人人都可以作卡米狗\ironman`。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-ZL43UH34UAXlwpFN5dIZgyjbBEQyN92eadc9Icl3aqv_AkrJE8f6VrW7S08dWK9UURNXEjX701gvN9RpRnK4U_2UWGChyphenhyphenOuTWxJVqbyUY3XhMiT8lGd9_GJkfDuU-hsOh3SixDgqNhc/s1600/5.jpg) 按下`選擇資料夾`。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7EAS3iZpjgIu7256kzEz4hECrVGF0X5oNRcVNldn3_V2MyJDIUgrRMe6AQpgA-jhbSEZvgcZdEww3sdg0XGJGzFjrkCRL4SNN9XCMNaiVCOdyJfIIkuNhstamqeSbKAsMJBEwNJm5Qxg/s1600/6.jpg) 左側多了一塊是資料夾目錄,那個灰色邊界是可以拖曳的,像這樣: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeblXfWUwiTu5EVnEYRPywNB558gi3umtgPJBuj1fOzolx5xPmSRGuhXQx2FdgFStGYyF6LyKUMwXXKQ587crB3MmS77cBcGxPqpByC9C5hrjwj6Uj7COBR1BwFwbusZdF0Q3-K19Jwpo/s1600/7.jpg) 如果覺得空間不夠大,可以自行調整。 我們的警衛室在 `config/routes.rb`,先點左側的 `config` 資料夾,點一下會展開,再點一下會收合,然後再點一下 `routes.rb`。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGr7MCEVhVUkFS-xOzpRY_fwWwXJSMettVkjAFsdyYhZdA7Frd1G2wxxYhlkm3fpUGXT8wAP1UALJ_ziH9Nh964B7RoVnAn63vaobONPG8QbPUpssO7L0a-2cbyi_J81yI5jnbKDbQJGU/s1600/8.jpg) 這樣就是空的。 # 那我們的住戶呢? 住戶在 `app/controllers`。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcyEaaQIF5MyNLftPwLwx9aWLHMBKtA3JEiVfD6qB6CJT3APmYVBeagPJaVbhMV9WFsH9Pbs07wbueM-NQAdvk1U449pHwD3f18S1xXKLVC9RvpaIYdMaScBtaSUZDo3z9GTRT4lcBWtg/s1600/9.jpg) 看到一個 concerns 空的資料夾和一個 application_controller.rb,現階段就當作沒看到這個檔案好了。 所以我們現在蓋的樓裡面根本沒住人!總之還是先弄個住戶進去再說。 # 做一個 Controller 先開啟 cmd 到我們的專案目錄: ``` D:\只要有心,人人都可以作卡米狗\ironman> ``` 輸入 `rails generate controller kamigo` ,請 rails 幫我們生成一個叫做 kamigo 的 controller。 ``` D:\只要有心,人人都可以作卡米狗\ironman>rails generate controller kamigo create app/controllers/kamigo_controller.rb invoke erb create app/views/kamigo invoke test_unit create test/controllers/kamigo_controller_test.rb invoke helper create app/helpers/kamigo_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/kamigo.coffee invoke scss create app/assets/stylesheets/kamigo.scss D:\只要有心,人人都可以作卡米狗\ironman> ``` 他幫我們產生了好多檔案,開頭有 create 的那些都是,但是其實我們只需要 `app/controllers/kamigo_controller.rb`。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjASP0Ei1jZ7qEw8pvzkIJcgaBiTBIV1l1iFRFZPuWP2QC_WefW4pZF3glczpSRMtXO4wLZsfhMqSfVftCpKysQJMO-_jsxo-26ZeiFCnPao1p9YBeyDwarX9TJ9wUX2acdPlxGpcn-wY0/s1600/10.jpg) # 我後悔了想復原怎麼辦? 我們可以用 `rails destroy controller kamigo` 來刪除這些檔案。 ``` D:\只要有心,人人都可以作卡米狗\ironman>rails destroy controller kamigo remove app/controllers/kamigo_controller.rb invoke erb remove app/views/kamigo invoke test_unit remove test/controllers/kamigo_controller_test.rb invoke helper remove app/helpers/kamigo_helper.rb invoke test_unit invoke assets invoke coffee remove app/assets/javascripts/kamigo.coffee invoke scss remove app/assets/stylesheets/kamigo.scss D:\只要有心,人人都可以作卡米狗\ironman> ``` # 這次我們手動新增 controller 在左側的 `controllers` 目錄上按右鍵。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhpdNu41HWxkUMPNcEGSWagKL6kdhUmLJ2nyBU_ETBX7bmBWhzZKUhWBsfllb9tsDMckjiAw97qZ2jhqBZbuaxwBEszv-ZuqQYuUsHukEtEcwF948QMDUoA4vl83ThPNJ9o0kj82VdFW0/s1600/11.jpg) 選 `New File`。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG1Dc8JslNB1pRKZfTDMYz0bXcXB5zw1VMCDPKBCbo7_unnKFSyrVigCHgSV3SPcP3NCVJIQOzjsFrcQK7Bi3mc9_vdpez4hyiTX9_up9q2HcqLu8nIHK6sDyuaey_DkMH9AYPt92vy-Y/s1600/12.jpg) 在新的檔案裡填入我們想要的程式碼: ``` class KamigoController < ApplicationController end ``` 然後按下 `Ctrl`+`S` 存檔。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgleLrv_RT_c5AjScQ-I94wKi3bXMJRj4KK8T3Fi8qtq0iUBID5BjMQ0TRbyD7tfPpPUErQzpD03i-vgtpCYaGuE8B-h7cp0lwfJiYCZxOPn3vSP-hunE2sMDZgv5QcBkP1El64N_xHLF0/s1600/14.jpg) 輸入檔名:`kamigo_controller.rb`,副檔名 `.rb` 是 ruby 的意思,這樣 sublime text 就能知道內文是 ruby 程式碼。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieHtEzaTFMXGX5L-Gsv-PY6CQ9gQk0JSVHp1i7GF8r3pD7ijkhyc3ARUkPLSCVRAuWkzfpGivkKiF-kDRkxTVgwc526t6vrEN3N5T_hd_qE14Zgu8bBFcxMMpxTX6NbKaZKSPfdAWhOU8/s1600/15.jpg) 自從我用了 sublime text 之後,人生就變成彩色的呢! 前面有提到 controller 是一戶,每一戶裡面還要有人,所以我們弄點人進去。 # 加入 action 我們定義一個叫做 `eat` 的空方法。 ``` class KamigoController < ApplicationController def eat end end ``` 注意看這裡: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhn1Ep3DQVa6acNQ6rWlrsCwBgeGLwUDlZg_NWZOAVHCNjHMKRwN6FiARp58Y1Pz1o-VbbwuWO-C77lh68brlnOi66g70vBdXMAktJoUy8gvz2n14Zqvg5jXCxko6xyhSXUK6YQHJx5BuE/s1600/16.jpg) 他是一個灰色圈圈,代表這個檔案編輯過,但還沒存檔,按下 `Ctrl`+`S` 之後就會變成灰色X,像這樣: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSHBBCCU9hHnnbkfgCmfMcp42P5WzyTmubKJeJjr2yA7D5U8e1ik3y2EveoZapyKykXVo4NknpaXzqrgM5mrUTUq2bcgj0WgEBS3S7gp9uNRF5voURcx74q70PBl3Ik7MhNK8Tcc1e5P8/s1600/17.jpg) 現在我們已經有一戶,而且住了一個人了,我們來請警衛幫我們轉信。 # 加入 route 我們把 `config/routes.rb` 改成這樣: ``` Rails.application.routes.draw do get '/kamigo/eat', to: 'kamigo#eat' end ``` `get '/kamigo/eat', to: 'kamigo#eat'` 的意思是當有人在瀏覽器輸入網址 `/kamigo/eat` 時,就把請求交給 kamigo 這個 controller 裡的 eat 方法來回應。 # 測試一下 在 cmd 輸入 `rails s` 開啟網頁伺服器: ``` D:\只要有心,人人都可以作卡米狗\ironman>rails s => Booting Puma => Rails 5.1.4 application starting in development => Run `rails server -h` for more startup options *** SIGUSR2 not implemented, signal based restart unavailable! *** SIGUSR1 not implemented, signal based restart unavailable! *** SIGHUP not implemented, signal based logs reopening unavailable! Puma starting in single mode... * Version 3.11.0 (ruby 2.4.2-p198), codename: Love Song * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stop ``` 用瀏覽器開啟這個網址:[http://localhost:3000/kamigo/eat](http://localhost:3000/kamigo/eat),然後就爆炸了: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHkka8-AzbNbUdz1GMWat2O_PwWaEuc5G5-zA0VsIZFMNrmHs9bOQAPwcQbnd59ARXCKVAMnfEJOvb9p-SNiGobUSfEKQCsiYpglju0SDoGL031BL8n_iTnwITx9MQQGTRzHZj0FIz17I/s1600/18.jpg) 是一個很眼熟的爆炸,這招我們在[第九天:作一個最簡單的 Rails 網站](https://ithelp.ithome.com.tw/articles/10194359)就玩過了。這是因為 Rails 找不到對應的 html 檔案,所以爆炸。可是我們並沒有在 eat 裡面要求 Rails 去找檔案呀!原來這是 Rails 的預設行為。要修好這個問題有兩個方法: - 在正確的地方新增一個網頁 - 為了避免 Rails 亂幫我們做事,我們得明確說明我們想幹嘛 兩種我們都要會。 ### 在正確的地方新增網頁 網頁應該要放在 `app/views/kamigo/` 下,檔名需要叫做 `eat.html` 或者 `eat.html.erb`。 在 sublime text 新增資料夾的方法是在 `views` 資料夾點右鍵: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1RaLqXOmoken4WK9olqnFq88vDTeWT7t9botyDu4uM3iZcwUWS4Gd2ZuthHsOlMbwtVDyItpMFDHUwbpTe3H6NdvkwmOcfyl3YwlTAEK3384xOj6AZjfVB3v4AGiNPShxx1lhqORvshs/s1600/20.jpg) 點選 `New Folder...`: ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1FT8GgwDYKjcf60yuI0ciaRW9V_Dh9nsK16m4oDHHJ9Jqd67nkOJdaHvdfvWLUS1xRQ3euSfDn2Jdh0Mvj8Yn6sGYWniRebGxZHyPWsMUzHfgxgQgZa_94femKkIrfy2ypj2RpQhw980/s1600/21.jpg) 輸入 `kamigo` 後按 `Enter`,資料夾就新增完成了。如果你不喜歡這樣,你也可以回到檔案總管去新增資料夾。 然後新增 `eat.html` 檔案,在裡面寫你想要吃的東西。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXlybjzHMgKMBoWa12PnKw3BztTVLt1zboLV5r9NsJs9bfuYh8Mxq4AooPXTiV1XetDkvPZJizsSHKxxkXwvqGaEGPe6Hb0-LHKyJfU7nzRWu9x-Q855iFycyxDVXGvwDJWucMeneXV5o/s1600/22.jpg) 再開一次網址看他有沒有修好:[http://localhost:3000/kamigo/eat](http://localhost:3000/kamigo/eat) ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz0o0IfSZZBF7gTYfXJ5GbuXu-c1DyqQ8HAmJl2mG15QJaTuV373Ycb6ONuXZM0tP-VCoHrKsLEz2pqGFYr9XfjCPy-jKPawJPTVh8PpRpdC7wnxuP-J3lUQHkIeqyZyovXfC1q4SFudA/s1600/23.jpg) 修好了! ### 為了避免 Rails 亂幫我們做事,我們得明確說明我們想幹嘛 把 `app/controllers/kamigo_controller.rb` 檔案改成下面的內容後存檔: ``` class KamigoController < ApplicationController def eat render plain: "吃土啦" end end ``` 我們加入了一行程式碼 `render plain: "吃土啦"`,意思是我們要用純文字`"吃土啦"`來回應這個請求,這樣 Rails 就不會用預設的網頁路徑作為回應了。 再開一次網址看他有沒有變化:[http://localhost:3000/kamigo/eat](http://localhost:3000/kamigo/eat)。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqGMNuknKbp8NPVilWr9REa8oY00fIjd_LGqyR8328383ZzfQ9TxSPXyYTgykiuJcZy959V8ggYpRp-lBfb3MPsWp-XjPdjLPJkG7lW6OyuRr_6mgrfn8OWSJv9qpIYtibjNfidLjonX0/s1600/24.jpg) 卡米狗好兇阿。 # 總結 - 今天了解到 Rails 最基本的運作流程 - 對於 Sublime Text 的操作又更加熟悉了 明天我們要從 Rails 的角度觀察 HTTP 協定。

2018/1/1

第十三天:認識 Ruby 的資料型態( 2018 iT邦幫忙鐵人賽-只要有心,人人都可以作卡米狗 )

markdown 今天要來學寫程式囉,今天就只講資料型態的部分,想要一天學完全部,我怕你的腦子會爆炸,畢竟在學校的話,這是一學期的課程。我們慢慢來就好,30 天之內一定會讓你學會寫聊天機器人的。 # 認識 irb 平常我們寫程式是寫在文字檔,然後再拿程式碼檔去一行行的執行程式。但當我們想要測試某一行程式碼的結果時,我們會使用 irb(Interactive Ruby) 來做測試。irb 很方便,我們可以在 irb 的環境裡面輸入一行程式碼,就能馬上得到這行程式碼的結果。 ### 進入 irb 先按 `Windows`+`R` 輸入 `cmd` 叫出小黑框: ``` D:\只要有心,人人都可以作卡米狗> ``` 輸入 `irb`。 ``` D:\只要有心,人人都可以作卡米狗>irb irb(main):001:0> ``` 輸入完之後就會進入 irb 的世界,在左側看到 irb 開頭就代表現在在 irb 裡面。 ### 離開 irb 要離開 irb 的話就輸入 `exit` 或者按 `Ctrl`+`C`。 ``` D:\只要有心,人人都可以作卡米狗>irb irb(main):001:0> exit D:\只要有心,人人都可以作卡米狗> ``` 就像這樣,就離開了 irb 的環境。 # 認識資料型態 再次進到 irb ,我們先認識一下資料型態: ### 數字 數字就是我們一般理解的數字,數學那種。 輸入 5566: ``` D:\只要有心,人人都可以作卡米狗>irb irb(main):001:0> 5566 => 5566 ``` 輸入 `5566` 之後看到 `=> 5566` 代表 `5566` 這行程式碼的執行結果是 `5566`。 接下來輸入 5566+9527 看看: ``` irb(main):002:0> 5566 + 9527 => 15093 ``` 輸入 `5566+9527` 之後看到 `=> 15093` 代表 `5566+9527` 這行程式碼的執行結果是 `15093`。 輸入 `5566.class` 可以讓 ruby 告訴我們,5566 是什麼型態: ``` irb(main):003:0> 5566.class => Integer ``` 5566 是 Integer(整數)。 ### 字串 輸入 `'5566'.class` 看看: ``` irb(main):004:0> '5566'.class => String ``` 用單引號或雙引號包起來的東西是字串,字串就是文字的意思。 輸入 `'5566+9527'` 看看: ``` irb(main):005:0> '5566+9527' => "5566+9527" ``` 因為加號也是文字的一部分,所以就不會做計算。 讓我們試著把兩個字串加起來看看,輸入`'5566'+'9527'`: ``` irb(main):006:0> '5566'+'9527' => "55669527" ``` 就像是在 5566 後面輸入 9527 一樣。 在 windows 的 irb 可能會遇到不能輸入中文的情形,目前請先別介意,我們用英文就好。 ### 變數 我們可以使用等號來幫數字或文字取名字。 ``` irb(main):007:0> idol = 5566 => 5566 ``` 變數定義完之後,我們只要叫到他,就視同輸入等號右邊的結果。 ``` irb(main):008:0> idol => 5566 ``` 事實上我們可以取名字的東西非常多,不是只有數字和文字能被取名。 ### 陣列 我們可以一次宣告大量變數,語法是中括號 [] 的中間用逗號區隔。 ``` irb(main):009:0> eat = ["Mcdonald's", 'KFC', 'BUG King', 'MOS BUG'] => ["Mcdonald's", "KFC", "BUG King", "MOS BUG"] ``` 我們這樣就宣告了4個字串變數,分別是: - eat[0] = "Mcdonald's" - eat[1] = "KFC" - eat[2] = "BUG King" - eat[3] = "MOS BUG" 在電腦的世界裡第一個數字是 0。,所以宣告一堆變數之後,第一個變數的索引是 0。 你也可以這樣寫,效果是相同的: ``` irb(main):010:0> eat = [] => [] irb(main):011:0> eat[0] = "Mcdonald's" => "Mcdonald's" irb(main):012:0> eat[1] = 'KFC' => "KFC" irb(main):013:0> eat[2] = 'BUG King' => "BUG King" irb(main):014:0> eat[3] = 'MOS BUG' => "MOS BUG" irb(main):015:0> eat => ["Mcdonald's", "KFC", "BUG King", "MOS BUG"] ``` 第一行是宣告一個空陣列,然後接下來的每一行都是塞入一個值。 你也可以這樣寫,效果也是相同的: ``` irb(main):016:0> eat = [] => [] irb(main):017:0> eat << "Mcdonald's" => ["Mcdonald's"] irb(main):018:0> eat << 'KFC' => ["Mcdonald's", "KFC"] irb(main):019:0> eat << 'BUG King' => ["Mcdonald's", "KFC", "BUG King"] irb(main):020:0> eat << 'MOS BUG' => ["Mcdonald's", "KFC", "BUG King", "MOS BUG"] ``` `<<` 表示放一個東西進入陣列。 查看 `eat` 的資料型態會發現資料型態是 Array(陣列)。 ``` irb(main):021:0> eat.class => Array ``` 查看 `eat[0]` 的資料型態會發現資料型態是 String(字串)。 ``` irb(main):022:0> eat[0].class => String ``` 跟一般的變數一樣,輸入 `eat[2]` 可以取得他的內容。 ``` irb(main):023:0> eat[2] => "BUG King" ``` 但是你還可以這樣: ``` irb(main):024:0> bgk = 2 => 2 irb(main):025:0> eat[bgk] => "BUG King" ``` 也許現在還很難理解把 `2` 換成 `bgk` 會有什麼好處,本篇結尾會說明。 # 雜湊陣列 在陣列中,我們學到可以用數字作為索引,而雜湊陣列則是把數字改成任何資料型態的變數。 雜湊陣列(Hash)是使用大括號作為定義: ``` irb(main):026:0> eat_hash = {} => {} irb(main):027:0> eat_hash[0] = "Mcdonald's" => "Mcdonald's" irb(main):028:0> eat_hash[1] = "KFC" => "KFC" irb(main):029:0> eat_hash[2] = "BUG King" => "BUG King" irb(main):030:0> eat_hash[3] = "MOS BUG" => "MOS BUG" irb(main):031:0> eat_hash => {0=>"Mcdonald's", 1=>"KFC", 2=>"BUG King", 3=>"MOS BUG"} ``` 你也可以這樣寫: ``` irb(main):032:0> eat_hash = {0=>"Mcdonald's", 1=>"KFC", 2=>"BUG King", 3=>"MOS BUG"} => {0=>"Mcdonald's", 1=>"KFC", 2=>"BUG King", 3=>"MOS BUG"} ``` 和陣列不同的是,雜湊陣列不需要從 0 開始。 而且雜湊陣列的特性是可以用不是數字的變數作為索引,其中最常用的索引是字串索引,舉例來說: ``` irb(main):033:0> kamigo = {} => {} irb(main):034:0> kamigo['Q'] = 'A' => "A" ``` 而這就是卡米狗學說話的核心原理。 卡米狗有一個很大的雜湊陣列,當有人教卡米狗說話時,我就會將關鍵字和要回應的內容儲存到雜湊陣列裡,像這樣: `kamigo[關鍵字] = 要回應的內容` 之後任何人說話,我就會去檢查 `kamigo[關鍵字]` 是不是有值,如果有值的話卡米狗就會做出回應。 講到這,應該就知道為什麼索引可以用變數代換是一件很重要的事情了吧?