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
  1. 首先在show action當中,:id代表的是從瀏覽器的http request傳送回來的id,為何不用字串id而要假掰的使用符號:id呢?因為id可能是一個字串變數,假如我在前面加上:

id = 10

那這樣使用字串id時,所代表的就不一定是我們從http request傳回來的那個id了,因此,使用:id可以確保開發者了解我們要抓取的是回傳的id。

  1. 同樣的道理,在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不一定是長這樣。

延伸閱讀

Ruby Learning

Understanding Symbols in Ruby

Ruby Doc