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
```
### 開啟網頁

壞掉囉,錯誤訊息是 `incompatible character encodings: ASCII-8BIT and UTF-8`。
下面那個深灰色區域是給我們除錯用的,有點類似 `irb`,我們可以在下面輸入一點東西:

我輸入了 `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"
}
```
### 圖解

昨天我們學的是 `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` 看一下差異。

因為這樣我們無法同時看到兩邊的結果,所以需要調整一下開發人員工具的位置,把他調到右側的話會比較恰當。
先點一下這裡:

再點一下這裡:

點完之後應該會變成這樣:

仔細一看,發現這兩塊是一樣的:

# 從 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)。

資料少很多,這表示 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
```

哈哈哈哈ㄚ哈
# 從 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 協定。

今天會詳細說明 Rails 是怎麼處理一個 HTTP Request。

當我們在瀏覽器輸入一個網址時,會發出一個 Get Request 到網頁伺服器。網頁伺服器會根據這個網址,決定他應該要幹嘛。而 Rails 中的 Route 負責分析網址, Route 會決定要把 Request 發給哪一個 Controller 的 Action 來做回應。因為 Action 畫上去圖會很亂,所以就乾脆不畫了。
應該很難懂吧?
用人話來說:Route 就像大樓警衛,當有信件送進來,他要負責把信交給對應的住戶,所以 Controller 就是住家,Action 就是裡面的人。

當然,一棟大樓會有一間警衛室,以及有很多住戶,而且每一戶裡面還有住很多人,但是一封信只會寄給一個人。
# 所以我說那個警衛室在哪?
先打開 sublime text,然後點左上角的 `File`(檔案)。

選擇 `Open Folder...`(打開資料夾),選到我們的專案資料夾:`D:\只要有心,人人都可以作卡米狗\ironman`。

按下`選擇資料夾`。

左側多了一塊是資料夾目錄,那個灰色邊界是可以拖曳的,像這樣:

如果覺得空間不夠大,可以自行調整。
我們的警衛室在 `config/routes.rb`,先點左側的 `config` 資料夾,點一下會展開,再點一下會收合,然後再點一下 `routes.rb`。

這樣就是空的。
# 那我們的住戶呢?
住戶在 `app/controllers`。

看到一個 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`。

# 我後悔了想復原怎麼辦?
我們可以用 `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` 目錄上按右鍵。

選 `New File`。

在新的檔案裡填入我們想要的程式碼:
```
class KamigoController < ApplicationController
end
```
然後按下 `Ctrl`+`S` 存檔。

輸入檔名:`kamigo_controller.rb`,副檔名 `.rb` 是 ruby 的意思,這樣 sublime text 就能知道內文是 ruby 程式碼。

自從我用了 sublime text 之後,人生就變成彩色的呢!
前面有提到 controller 是一戶,每一戶裡面還要有人,所以我們弄點人進去。
# 加入 action
我們定義一個叫做 `eat` 的空方法。
```
class KamigoController < ApplicationController
def eat
end
end
```
注意看這裡:

他是一個灰色圈圈,代表這個檔案編輯過,但還沒存檔,按下 `Ctrl`+`S` 之後就會變成灰色X,像這樣:

現在我們已經有一戶,而且住了一個人了,我們來請警衛幫我們轉信。
# 加入 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),然後就爆炸了:

是一個很眼熟的爆炸,這招我們在[第九天:作一個最簡單的 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` 資料夾點右鍵:

點選 `New Folder...`:

輸入 `kamigo` 後按 `Enter`,資料夾就新增完成了。如果你不喜歡這樣,你也可以回到檔案總管去新增資料夾。
然後新增 `eat.html` 檔案,在裡面寫你想要吃的東西。

再開一次網址看他有沒有修好:[http://localhost:3000/kamigo/eat](http://localhost:3000/kamigo/eat)

修好了!
### 為了避免 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)。

卡米狗好兇阿。
# 總結
- 今天了解到 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[關鍵字]` 是不是有值,如果有值的話卡米狗就會做出回應。
講到這,應該就知道為什麼索引可以用變數代換是一件很重要的事情了吧?
訂閱:
文章 (Atom)