Ruby及Rails當中的:symbol代表什麼意思?
在學習Rails時,不免剛開始就會看到一堆冒號開頭的東西,例如:post、:model、:controller、:action等等,如果你跟我一樣是程式語言的新手,腦袋一定是滿滿的WTF!
這其實是Ruby當中的symbol符號,會用冒號:
當做開頭。簡單說明一下為何會有符號這個東西:
Ruby每一個物件都是新的物件
大家都知道Ruby是一個物件導向的語言,也就是說每一個變數、文字、數字都是物件,所以我們才可以寫出:5.times { puts "hello" }
這樣簡單直白的語句,5
是一個物件,所以才可以在後面加上method。
大家都是物件,很棒,但這跟符號有什麼關係呢?Ruby在處理每一個物件時,會在記憶體中產生一個新的物件,所以每一個東西都不相同,這點可以使用object_id這個method來證明:
"hello".object_id
# => 70318367784340
"hello".object_id
# => 70318367777740
大家可以發現,明明都是同一個字串hello,為何會有不同的object_id呢?這是因為對Ruby來說,每碰到一個新的東西,都是讓Ruby去讀取、寫入記憶體,也因此每個物件都是不同的物件。也就是為何Rails要強調DRY原則,不撰寫重複的程式碼。只要有重複的程式碼出現,不只在修改和維護上會有困難,更重要的是讓Ruby會去讀取多餘的記憶體,降低效能。
如果我們有重複的字串,務必使用變數來儲存:
a = "hello"
a.object_id
# => 70318367761840
a.object_id
# => 70318367761840
這樣就能不重複產生新物件。
Ruby符號是獨特的物件
有了剛剛的觀念,現在就可以提到,符號是一個獨特的物件,每次我們重複提到符號,他並不會消耗多餘的記憶體。
:hello.object_id
# => 538888
:hello.object_id
# => 538888
當我們使用符號時,我們使用的是同一個符號,而非像字串一樣會產生新的物件。
Ruby method會自動產生symbol
在定義method時,Ruby會自動幫我們為method產生一個symbol,例如:
class Greeting
def self.hello
puts "hello world"
end
end
Greeting.hello
# => "hello world"
Greeting.send(:hello)
# => "hello world"
say_hello = Greeting.method(:hello)
say_hello.call
# => "hello world"
先定義好class及method之後,使用以上三種呼叫method的方法都可以呼叫hello這個method。
在例子當中可以發現一件事情,也就是當提到:hello時,他所對應到的就是我們定義好的hello method。是一個『所見即所得』的概念,也就是說我們可以指定不同數值給一個變數,但提到符號時,所指的就一定是一個定義好的method。
為何Rails會常常帶入symbol到method當中
我們可以從『所見即所得』延伸回Rails,在Rails當中常常看到一些很奇妙的地方會帶入符號,例如controller當中:
def show
@post = Post.find(params[:id])
end
def create
@post = Post.create(post_params)
if post.save
redirect_to posts_path
else
render :new
end
end
- 首先在show action當中,:id代表的是從瀏覽器的http request傳送回來的id,為何不用字串
id
而要假掰的使用符號:id
呢?因為id可能是一個字串變數,假如我在前面加上:
id = 10
那這樣使用字串id時,所代表的就不一定是我們從http request傳回來的那個id了,因此,使用:id可以確保開發者了解我們要抓取的是回傳的id。
- 同樣的道理,在create action當中,可以看到要是儲存失敗的話,就會回到new的畫面。這裡同樣因為我們有先定義過new這個method,所以可以直接使用:new來當做呼叫method的方法。為何要用符號?同樣是為了『所見即所得』的概念,使用symbol來代表那個我們要使用的東西。
Rails當中帶入符號的方法
在Rails的其他地方,常常可以看到類似的用法,例如在migration當中我們會看到:
add_column :post, :content, :string, :limit => 255
許多Ruby和Rails的method都是用這種方法來帶入變數,簡單解釋一下,通常在source code的某處,都會有這樣的method存在:
def add_column(table, column, type, *options)
# 利用table變數找到table
# 利用column變數創造一個欄位
# 利用type變數確定column的屬性
# 檢查是否還有額外的options變數
end
add_column這個migration是為我們在資料庫中新增欄位,因此我們帶入的都是符號而非變數,代表我們所描述的內容。定義method的方法有很多種,但每次要帶入大量符號時,大概就可以了解是這種模式在運作。
另外,由於我本人並沒有閱讀這段的source code,因此add_column這個method不一定是長這樣。