【跨程式語言上手】系列第一篇,最近換工作學習了 Golang / Ruby,可以說是設計理念處於對立面的程式語言,在學習中不斷拿兩者比較,找到很多有趣的地方
在這樣的過程中,慢慢找出自己上手新程式語言的 pattern,也就是逐步填補自我疑問的過程,像是「怎麼宣告變數?」「怎麼寫測試?」「api server / http request 怎麼發?」「併發或效能怎麼處理?」等等共通的疑惑點
坊間有很多善心人士的教學,但往往都缺一點我想了解的資訊,例如 Google 搜尋「Ruby 教學」的中文素材,跑出這幾個很棒的教學
- 高見龍大大的 為你自己學 Ruby on Rails
- Ruby on Rails 實戰聖經
- Ruby 官網的 二十分鐘 Ruby 體驗
Ruby 教學會被綁在 Rails 教學中或許是 Ruby 生態特有的現象,但普遍程式語言教學也都缺少
- Testing:包含 Testing Framework / Unit Test / Mock、Stub
- Module:Core Module / Local Module 怎麼載入
- Concurrency:底層如何處理併發/平行運算
- 程式語言多用於後端,所以我會在意寫 api server / http request 的感覺是怎樣
可以說這幾點都是比較進階/偏科的議題,但對我的工作很重要,也是這系列的起源,我想要重新寫一份對我自己來說完整的程式語言教學
,過程中會拿我已經熟悉的程式語言如 Javascript 跟一點點的 Golang 做對比
目標會著重於有經驗的程式設計師,已經熟練任一程式語言,想要快速上手或是品味另一門語言的人
以下內容會包含
- Ruby 設計理念與起源
- 基礎語法
- 模組
- 測試
- Http / API 相關
- 其他補充
內容大量參考上附的參考資料,並融入自己的淺見,會隨著使用時間的拉長持續修改,有什麼不同的意見歡迎留言分享
1. Ruby 設計理念與起源
Ruby 是一門 Dynamic Language,運行在 Ruby Virtual Machine 上,本身是弱型別但沒有 JS 中隱式的轉型 (ex. 1 + “23”),在 3.0 加入 Type safety 工具 TypeProf
幫助檢查型別問題
透過範例簡單看一下 Ruby 幾個特別的設計理念
Ruby is designed to make programmers happy
出自於 The Philosophy of Ruby A Conversation with Yukihiro Matsumoto, Part I,Ruby 給予開發者很高的自由度
- 定義 symbol 在 Ruby 的框架下產生自己的 DSL,例如 sinatra 這個 web framework,看範例會以為根本不是 ruby 寫的
- 支援 Meta programming 可以在 Runtime 改變類別行為
- 可以複寫任意的方法,包含原生類別
- 同一種功能可以有非常多種寫法,光是迴圈可以用 while / for in / each / until / begin while 等
Seeing Everything as an Object
在 Ruby 的世界中,幾乎每一個變數都是物件,包含 1+2
也可以寫成 1.+(2)
,1 本身是 Integer 類別
裡頭有 + 這個方法
這讓 Ruby 很適合 OOP,也帶來很多的彈性,像是在 operator overwrite
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Integer
alias :plus :+
def + (other)
puts self.to_s + " is adding " + other.to_s
self.plus other
end
end
puts 1 + 2
# 輸出結果
# 1 is adding 2
# 3
|
Integer 是預設類別,Ruby 遇到類別重複宣告時會合併
,接著我們在 Integer 宣告 plus 是原本 + 的別名,接著覆寫 +
先打印出 is adding 字串在回傳,在 Ruby 中預設 function 最後一行即使不顯式宣告也會 return
透過匿名函式支援 Functional Programming Style
在 Ruby 世界中,不像 Javascript / Golang 把 function 視為一等公民
在程式語言中,所謂的一等公民條件是
- 可以傳入 function 當作參數
- 可以被 function 當作 return 值
- 可以被儲存於資料結構中使用
但是 Ruby 也還是匿名函式的語法,大致如下
1
2
3
4
5
6
7
8
9
| def sum(x)
total = x
proc { |y| x += y }
end
sum_five = sum 5
puts sum_five.call(5)
puts sum_five.call(5)
puts sum_five.call(5)
|
後續會有更詳細補充,但至少 Ruby 世界中也是可以做到 functional programming 的
綜合以上,Ruby 是一門彈性很大、很自由的語言,這是一把雙面刃,對於新手可能也不是這麼友善,畢竟有太多語法跟關鍵字要去熟悉
如何安裝
可以從官網下載安裝 Installing Ruby,或是先安裝 Ruby 版本管理工具如 RVM
套件管理
安裝完 ruby 後,也同時安裝了 gem
,gem 是 ruby 套件管理工具,可以安裝或發佈自己的套件
gem 我一開始理解成 npm,但 npm 層級高了一些,例如 gem 並沒有做到版本控制的功能,gem + bundle 比較是 npm 的組合
詳細可參考 Ruby 的 Rvm VS Gem VS Bundler 的差別
基礎語法
變數宣告
- 不用宣告型別
- 變數可以改變型別
- 但是沒有隱式的型別轉換
- 變數的
scope 只有當前的 context
,但要注意匿名函式會讀取當前的 context,並不會一直往上查找,除非用全域變數 $
開頭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| x = 123
x = "123"
x = 123 + "123" # 拋錯 TypeError (String can't be coerced into Integer)
sum = 5
def func
puts sum # 拋錯
end
1.upto(5) { |i| sum += i } # 這樣是可以的,因為 block 是用宣告當前的 context
$sum = 5 # 全域變數可以
def func
puts $sum
end
|
命名規則
- 變數名稱常用蛇形命名法
- 變數全大寫代表
常數
,但是常數被改會有 warning 不會有錯誤 - Class/Module 名稱開頭大寫
1
2
3
| naming_convetion = 123
FIVE = 5
FIVE = 4 # warning: already initialized constant X
|
Symbols
建立唯一且不可變的物件,用 :
開頭,重複宣告都會指向同一份記憶體位置 (透過 object_id 識別
),而字串每一次宣告都會在記憶體產生新的一份 String Object,如果是要單純用來識別 Symbol 效能會比 String 好上很多喔
1
2
3
4
5
6
7
8
9
| hello = :hello
world = :hello
puts hello == world
puts hello.object_id == world.object_id #true
hello = "hello"
world = "hello"
puts hello == world
puts hello.object_id == world.object_id #false
|
Hash
Ruby 有 Hash,可以用 => 或 :
分隔 key value,但是兩者有很大的差異
- => 非常的自由,key 值可以是任意的值
- : 的 key 只能是 symbol,如果放字串會直接轉成 symbol
要非常小心 string 跟 symbol
是不同的,實作上很容易踩到這個坑
1
2
3
4
5
6
7
| a = { "123": "123" }
b = { "123" => "123" }
puts a["123"] # nil
puts a[:"123"] # "123"
puts b["123"] # "123"
puts b[:"123"] # nil
|
Ruby 中幾乎都是物件,有內建很多便利的方法
1
2
3
4
5
| x = 1
puts x.methods # 列出所有 Integer 包含的 method
puts x.odd? # 是不是偶數
puts x.class # 類別
puts x.to_s # 轉成字串
|
陣列
1
2
3
4
5
| arr = [1,2,3,4,5]
puts arr.include?(2)
puts arr.push 0
puts arr.pop
|
loop / control flow
條件式
基本的 if / elseif / else 與三元判斷式
1
2
3
4
5
6
7
8
9
10
| x = 1
if x.odd?
puts "x is odd"
elsif x.even?
puts "x is even"
else
puts "never happen"
end
puts (x.odd?) ? "x is odd":"x is even"
|
switch case
- 採用 case / when / else 語法,不用加 break
- case 如果沒有接參數,則 when 條件可以放 statement / 如果有接參數,則 when 條件放常數
- 如果希望 case when 結果賦值給變數,可以用 when … then
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| x = 1
case
when x.odd?
puts "x is odd"
when x.even?
puts "x is even"
else
puts "never happen"
end
case x
when 1..10
puts "x is in 1 to 10"
else
puts "x is not in 1 to 10"
end
val = case x
when 1..10
then "x is in 1 to 10"
else
"x is not in 1 to 10"
end
# val = "x is in 1 to 10"
|
迴圈
方式很多種
- 先檢查條件的 for in / while / until
- 後檢查條件的 begin … (when/until)
- Enumerable 物件可以用 each
但沒有常見 for(initialExpression; conditionExpression; incrementExpression)
宣告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| arr = [1,2,3,4,5]
i = 0
while i < arr.size do
puts arr[i]
i += 1
end
i=0
until i >= arr.size
puts arr[i]
i += 1
end
i=0
begin
puts arr[i]
i += 1
end while i < arr.size
for i in arr do
puts i
end
arr.each { |x| puts x }
|
Error handling
- 透過 raise 拋出錯誤
- 透過 rescue 接錯誤,可以更進階指定錯誤類型
1
2
3
4
5
6
7
8
9
10
11
12
13
| begin
#... process, may raise an exception
raise ArgumentError
rescue ArgumentError
puts "ArgumentError"
rescue => error
puts error
#... error handler
else
#... executes when no error
ensure
#... always executed
end
|
進階資料請參考 How to Rescue Exceptions in Ruby
Function
- function 不用定義回傳值
- 呼叫可以省略括號
- 預設最後一行會回傳,不用在顯示宣告 return
- 如果呼叫的參數數量跟宣告不同會拋出錯誤
1
2
3
4
5
| def add (a, b)
a + b # 等同於 return a + b
end
puts add 1,2
|
Class
- 預設 Class 名稱開頭大寫
- 支援繼承
Successor < Predecessor
- 要建立 instance 透過
Class.new
,會呼叫 class 中的 private method initialize
- 宣告 instance 變數以
@
開頭 / 宣告 class static 變數用 @@
- 需要顯式指定針對 instance 變數的 getter/setter,或是用
attr_accessor/attr_writer/attr_reader
增加 - 支援 public/protected/private,但跟其他語言的 private 不太同,以下節錄自高見龍大大的文章
因為在 Ruby 裡所謂的 private 方法的使用規定很簡單,就只有一條:「不能明確的指出 receiver」。用白話文講,就是「在呼叫 private 方法的時候,前面不可以有小數點」。也就是因為這樣,在 Ruby 的 private 方法其實不只類別自己內部可以存取,它的子類別也可以,並沒有像其它程式語言一樣的繼承限制
- 定義 static method 可以用
def self.method
,或是用 class << self
沒有 interface / method overloading / polymorphism
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| class Person
attr_accessor :name
def initialize(name, age)
@name = name
@age = age
end
def hello
puts "Hello, my name is #{@name}"
end
def self.show_specy
puts "we are Mammals"
end
=begin 等同於上者,此方法適和於大量定義
class << self
def show_specy
puts "we are Mammals"
end
end
=end
protected
def my_protected_method
puts "my protected method"
end
private
def my_little_secret
puts "private methods"
end
end
Person.show_specy
p1 = Person.new("yoyo", 10)
puts p1.name
p1.name = "hello"
# puts p1.age 對應第5點,不能直接呼叫 .age 取得 age 變數
class Teacher < Person
def initialize(name, age, major)
super(name, age)
@major = major
end
def can_access_parent_private_method
self.my_protected_method
my_little_secret # 這樣可以讀取 parent private method
#self.my_little_secret
end
end
t1 = Teacher.new("Mark", 10, "English")
t1.hello
t1.can_access_parent_private_method
t1.send :my_little_secret
t1.send :my_protected_method
|
Ruby 物件比想像中複雜,尤其是支援 Meta programming,進階資料可以參考 Ruby 的繼承鍊 (1) 物件導向如何實踐
匿名函式 block / Proc / lambda
- block 代表程式碼區塊,少數 Ruby 中
不是物件
的存在,必須依附在 function 上,透過 yeild 呼叫 block 執行,單行宣告用 {...}
,多行用 do ... end
- Proc 是物件,不限制參數,return 時是代表當時的 context return,透過 proc.call 執行
- lambda 是特殊的 Proc,會嚴格檢查參數,return 就如同一般的 function return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| 1.upto(5).map {|ele| puts ele}
# 可以判斷是不是有 block 被傳入,如果有則用 yeild 呼叫執行
def hello
if block_given?
yield("world")
end
end
hello do |x|
puts "message from hello: #{x}"
end
# Proc / lambda 可以儲存於變數備用
my_proc = Proc.new{ |x|
return puts "from proc #{x}"
}
my_lambda = lambda { |x|
puts "from lambda #{x}"
}
def func(block)
puts "before call"
block.call("func")
puts "after call"
end
func(my_lambda) # before call/n from lambda func/n after call
# proc 宣告於最上層 context,所以 return 時會連帶結束整個程式
func(my_proc) # before call/n from proc func
|
有趣的語法 &:symbol
網路上有些說法是 {|x| x[:symbol]}
的縮寫,讓我們看下去
1
2
3
4
5
6
7
8
9
10
11
12
| class Person
attr_reader :name
def initialize(name)
@name = name
end
end
a = [ Person.new("123"), Person.new("2222") ]
name_list = a.map { |x| x.name } # 兩者相同
name_list = a.map(&:name)
p name_list
|
- 參數用 & 開頭代表參數是以 Proc 傳入,他跟一般的參數 args是切開的
1
2
3
4
5
6
7
8
9
10
| def some_method(*args, &block)
puts "args: #{args.inspect}"
puts "block: #{block.inspect}"
end
some_method(1,2,3, :whatever)
# args: [1,2,3, :whatever]
# block: nil
some_method(1,2,3, &:whatever)
# args: [1,2,3]
# block: #<Proc:0x007fd23d010da8>
|
- 在 Ruby 呼叫 object method 可以用 send 的方式
1
2
3
4
5
6
7
| class Person
attr_reader :name
def initialize(name)
@name = name
end
end
p Person.new("123").name == Person.new("123").send(:name)
|
- &:symbol 實際上會去呼叫 :symbol#to_proc ,而在 Symbol 中有定義 to_proc 行為,也會有人去自定義 class 中的 to_proc 方法
1
2
3
4
5
6
7
| class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
|
綜合上述,可以拆解成
1
2
3
4
5
6
7
| a = [ Person.new("123"), Person.new("2222") ]
puts a.map(&:name)
# 可以拆解成下者
puts a.map { |x|
x.send(:name)
}
|
也就是 receiver 會收到 symbol 的方法呼叫,參考自 What does map(&:name) mean in Ruby?
其他優秀的資訊
- Ruby 探索:Blocks 深入淺出
- [Ruby] 如何理解 Ruby Block
Concurrency & Parallelism
- Ruby 支援 Process ,可用於處理 CPU-Heavy issue
Ruby 在 2.0 導入 Cope on Write,當 process fork 時如果 value 沒有改動則使用同一份記憶體空間,降低 fork 對於記憶體資源無謂的佔用
- Ruby 支援 Thread,適用於處理 IO Event,但如果是 CPU-Heavy issue 則沒有幫助,因為 Ruby 有 GIL (Global Interpreter Lokc) 所以
無法併發,一次只能執行一個 thread
,這跟 Ruby VM 實作有關,如果是 JRuby 則沒有此問題
Thread releases GIL when it hits blocking I/O operations such as HTTP requests, DB queries, writing / reading from disk and even sleep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| require 'benchmark'
ELE_AMOUNT = 1000
PROCESS_NUM = 2
arr = Array.new(ELE_AMOUNT) { Array.new(ELE_AMOUNT){rand(1...9)}}
Benchmark.bm(10) do |bm|
bm.report("seq") do
total = arr.reduce(0) do |sum, ele|
sum + ele.sum
end
end
bm.report("parallel") do
read, write = IO.pipe
1.upto(PROCESS_NUM).map do |i|
Process.fork do
p i
step = (ELE_AMOUNT * 1.0 / PROCESS_NUM).ceil
start_ele = step * (i-1)
total = arr[start_ele, step].reduce(0) do |sum, ele|
sum + ele.sum
end
write.puts total
end
end
Process.wait
end
bm.report("thread") do
total = 0
threads = []
1.upto(PROCESS_NUM).map do |i|
t = Thread.new do
step = (ELE_AMOUNT * 1.0 / PROCESS_NUM).ceil
start_ele = step * (i-1)
total += arr[start_ele, step].reduce(0) do |sum, ele|
sum + ele.sum
end
end
threads << t
end
threads.each(&:join)
end
end
|
- Fiber 是類似於 goroutine 的概念,更輕量的 user space thread,主要用來非同步的排程,適用於結合 Non-blocking IO,因為 Fiber 在 Context Switch 比 Thread 更為輕量
範例來源:Introduction to Concurrency Models with Ruby. Part I
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| EventMachine.run do
Fiber.new {
page = http_get('http://www.google.com/')
if page.response_header.status == 200
about = http_get('https://google.ca/search?q=universe.com')
# ...
else
puts "Google is down"
end
}.resume
end
def http_get(url)
current_fiber = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback { current_fiber.resume(http) }
http.errback { current_fiber.resume(http) }
Fiber.yield
end
|
- Ruby 3.0 導入了基於 Actor 模式的
Ractor
,真正能做到利用 Thread 達成 Parallelism,以前會需要 GIL 是為了避免 multi thread 之下的 deadlock / race condition 狀況,但是在 Ractor 中基本上 Object 都不會被共享,參考 Share Memory By Communicating
有些文章會寫 Guild,但我查 Ruby 官方文件只有看到 Ractor
Do not communicate by sharing memory; instead, share memory by communicating.
意即如果希望在多個 Thread 中共享資訊,不要透過共享記憶體來溝通,而是透過通信交換資料達到共享資料的目的
因為共享記憶體就必須處理 lock,接著就要擔心 dead lock 等問題
如果不共享記憶體,直接將資料透過通道等方式傳遞,就不用擔心以上的問題,也可以更好的並行運算
優良的資源參考
- Ruby Concurrency and Parallelism: A Practical Tutorial
- Introduction to Concurrency Models with Ruby. Part I / II
- RubyConf Taiwan 2019 - The Journey to One Million by Samuel Williams
Module
- Module 提供類似於 Namespace 角色,可以自定義方法與常數不用擔心與其他人衝突
- Module 不能被實體化 (也就是不能被 new),主要透過 mixin 擴充 Class
在共享實作方面,Ruby 一個 Class 只能繼承一個 Parent,但是 Module 可以 mixin 多個,在沒有直接關係的情況下想要跨多個 Class 共享某些特定的方法,Module 是不錯的選擇
- 使用 Mixin 要小心多個 Module 可能會有不預期的互相干擾,例如修改同一個 instance 變數,有狀態要紀錄記得取一個比較特殊的名稱
- 如果 Module 中有 Class 宣告,要指定該 Class 可以用雙冒號
::
連接例如 Module Name::Class Name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| ##### calculator.rb
module Greeting
def sayhi
puts "hello, #{@name}"
end
class Hi
end
end
###### main.rb
require_relative './calculator'
class Person < Greeting::Hi
# 透過 include 達到 mixin 效果
include Greeting
def initialize(name)
@name = name
end
end
p = Person.new("yoyo")
p.sayhi
|
Module 除了可以被 include 外,還有 extend 跟 prepend,請參考 Ruby 的繼承鍊 (2) — Module 的 include、prepend 和 extend
程式碼拆分檔案
把全部程式碼塞在同一個檔案十分的可怕,透過適時的拆分可以讓程式碼更好維護,在 Ruby 中如果要 include 其他檔案的宣告,可以用 require_relative '檔案本身的相對路徑'
的方式
目前看起來只有常數、Module、Class 會被自動 export,還再找到相關的文件說明
require 總共有幾種
require
:
- 如果是相對路徑,則根據
$LOCAL_PAHT
設定去找對應的 library,通常是用來找外部相依或是 gem - 如果是絕對路徑,則直接載入對應檔案
require_local
:
透過檔案的相對路徑找到檔案,主要是用於在自己專案中的其他檔案
Testing
Ruby 並沒有內建的 Test Framework,評估後選用 RSpec,提供 TDD/BDD Style 語法,透過 describe / it 組合測試案例
- 慣例是專案根目錄建立 spec 目錄,待測項目對應檔案名加上 _spec 結尾
Unit Test
- 支援 before / after / around(before + each),執行頻率分成 each / all / suite
suite 是在整個 test file 只會跑一次,all 則是每個 describe 都會執行一次
- 如果是有變數要在每一次 test case 前執行,可以用 before(:each),或是用
let(變數名稱){回傳值}
以下是一個簡單的運費計算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| require('rspec')
require_relative '../calculator'
describe Calculator do
let(:calculator) { Calculator.new }
it 'small package should get $100 fee' do
expect(calculator.fee(1, 5)).to eq(100)
end
it 'medium size should get $500 fee' do
expect(calculator.fee(10, 5)).to eq(500)
end
before do
@calculator = Calculator.new
end
around do |t|
p "before each test"
t.run
p "after each test"
end
it 'large size should get $700 fee' do
expect(@calculator.fee(100, 10)).to eq(750)
end
end
|
Stub / Mock / Spy
RSpec 提供 mock 方法叫做 double (出自於 stunt double 演員替身)
- double 可以憑空產生物件,可以自定義 function 與回傳值
- double 可以只覆寫特定方法的回傳值
allow(instance).to receive(method).and_return(value)
- spy 不改方法的實作,只確認有沒有被呼叫,以及確認傳入的參數以及呼叫順序是否如預期
- 每一個 test case 後都會被自動 restore
- 因為是動態語言,所以要 “double(string)” 等方法都是可以的
運費計算加上一個 VIP 檢查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class VIP_Service
def is_vip?
raise "do some query"
end
end
describe
let(:calculator) do
vip_service = VIP_Service.new
# 這裡透過 mock 動態改變
allow(vip_service).to receive(:is_vip?).and_return(false)
Calculator.new(vip_service)
end
end
|
spy 的案例請看官方文件範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| require 'rspec'
class Invitation
def deliver(email)
p email
end
end
describe "Invitation" do
let(:invitation) { spy("invitation") }
before do
invitation.deliver("foo@example.com")
invitation.deliver("bar@example.com")
end
it "passes when a count constraint is satisfied" do
# 透過 have_recieved 看方法有沒有被呼叫
expect(invitation).to have_received(:deliver).twice
end
it "passes when an order constraint is satisifed" do
# 加上 with 檢查方法呼叫時傳入的參數
# 加上 ordered 代表要按照此順序呼叫
expect(invitation).to have_received(:deliver).with("foo@example.com").ordered
expect(invitation).to have_received(:deliver).with("bar@example.com").ordered
end
end
|
HTTP Request & API Server
Ruby 並沒有提供 http server 的封裝,只有 tcp server,所以我們選用 Rack 這一套 library,他被 Ruby 生態圈廣泛採用的底層框架,如 RoR 也是
Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby.
API Server
新增 server.ru,透過 $rackup server.ru
啟動
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| require 'rack'
require 'json'
rack_proc = lambda { |env|
req = Rack::Request.new(env)
case req.request_method + ":" + req.path_info
when "GET:/hello"
return [200, { "content-type"=> "application/json" }, [ { "hello" => req.params["name"] }.to_json ]]
when "POST:/world"
body = JSON.parse req.body.gets
return [200, { "content-type"=> "application/json" }, [ { "hello" => body["name"]}.to_json ]]
else
return [406, { "content-type"=> "html/txt" }, ['not_implemented_yet']]
end
}
run rack_proc
|
HTTP Request
參考 5 ways to make HTTP requests in Ruby
1
2
3
4
5
6
7
| require "http"
response = HTTP.get("http://localhost:9292/hello", :params => {:name => "world"})
p response.parse
response = HTTP.post("http://localhost:9292/world", :body => {:name => "jojo" }.to_json)
p response.parse
|
結語
寫過最長的文章,整理了這一個禮拜上手 Ruby 的過程,Ruby 實際上還有非常多的 黑魔法
,跟新同事們一起上手的過程不斷發現驚喜(恐?!)
必須坦白說上手到現在沒有很愛 Ruby,因為太自由了,又有太多關鍵字跟寫法,可能要在一段時間熟悉,之後再來慢慢補充