Chapter 3: Methods - Part III
還記得在 Chapter 2 提過的 “method lookup” 嗎?是否有想過當不存在的方法被呼叫時,Ruby 的方法查詢流程會變得怎麼樣呢?今天就要來介紹 Ruby 在 BasicObject 內建的一個方法 - Method Missing
Method Missing
Method missing 是 BasicObject 內建方法,可讓你攔截找不到方法時或是無法回應物件時的ㄧ 種安全機制。 我們知道在 Ruby 中所有類別都是繼承自 BasicObject,因此每一個類別都擁有 method_missing() 方法。這個方法存在的目的是當 Ruby 找不到呼叫的方法或是無法回應當物件時,就會呼叫 method_missing() 方法。透過方法複寫來重新定義 method_missing(),可以攔截原有的 method_missing() 方法 ,達到精簡程式碼的效果。
以下面程式碼為例:
class Animal
def jump
p "jumping"
end
end
dog = Animal.new
dog.jump # => jumping
我建立了一個 Animal 類別,裡面有 jump() 實體方法,當 dog 物件呼叫 jump() 方法,回傳 “jumping” 就是正確的值。
但如果我們叫 dog 物件說話呢?
dog.speak # => undefined method `speak' for ... (NoMethodError)
很明顯地 dog 物件並沒有 speak() 方法,所以回傳 (NoMethodError) 其實是正確的錯誤訊息。 你也可以利用開放類別的技巧,覆寫 method_missing()方法,讓回傳的錯誤訊息更加獨特。
根據 Ruby 官方手冊中的 method_missing 方法:
method_missing(symbol [, *args] )
symbol: 這裡符號代表的是方法名稱,是必須要有的參數。 *args: 任何其他傳進來的引數
現在來試試覆寫 method_missing()
class Animal
def jump
p "jumping"
end
def method_missing(method_name, *args)
# 因為傳進來的是符號,比對之前要先轉換成字串
if method_name.to_s == 'speak'
p "我不會說話"
else
# 如果比對失敗,回傳原本的錯誤訊息 (NoMethodError)
super
end
end
end
dog = Animal.new
dog.jump # => "jumping"
dog.speak # => "我不會說話"
dog.talk # => (NoMethodError)
- 當 jump() 方法被呼叫時會遵循ㄧ般的 method lookup 流程,在 Animal 類別找到該方法。
- 當 speak() 方法被呼叫時,會找不到該方法,所以回到我們覆寫 method_missing 方法,並且在比對成功後,回傳 “我不會說話”。
- 當 talk() 方法被呼叫時,會找不到該方法,所以回到我們覆寫method_missing 方法,並且在比對失敗後,super 會呼叫跟 dog物件血緣最近的父類別的 method_missing()方法,一直延著繼承關係往上找,但是一直都沒找到,所以回傳原本的錯誤訊息 (NoMethodError)。
Ghost Methods
當你有需要定義很多類似的方法時,可以有目的地把這些方法整合起來讓 method_missing() 去處理。這有點像是跟物件說:"如果有人問你這個方法,你不懂的話就去做這件事"
對傳送者來說,其實有沒有經由 method_missing() 方法所得到訊息,並無太大差別,因為也是在物件上找到的方法,再回傳訊息而已。但是對接收者而言,從 method_missing() 方法傳出來的訊息是經過確認,沒有存在相對應的方法時才發出的。
物件無法回應或是不存在的方法就稱為 Ghost Methods,而有目的地去呼叫這些 Ghost methods,再經由 method_missing() 所動態產生的對應手法就稱為是 Dynamic proxies。
Method Missing In Rails
相信寫過 rails 的朋友們對 find_by 方法都很熟悉,但其實 ActiveRecord 還提供了另一種寫法就是:
YourModel.find_by_AttributeName("value")
User.find_by_id(1) # => return User with id equal to 1
User.find_by_id(0) # => return nil if User cannot be found
你也可以將 id 換成其他表格內的屬性,例如:名字、年齡、等…
User.find_by_name("kevin")
User.find_by_age(16)
...
這種 find_by_AttributeName的寫法其實就使用的 method_missing 方法的技巧。
ActiveRecord 會去找 find_by 字尾的屬性,然後與資料庫的欄位做比對,如果比對成功,就回傳該筆資料。這就與先前在 Animal 類別內覆寫 method_missing() 方法雷同,只有當比對失敗時,會跑 super 繼承關係流程,最終 nil 值 或是 錯誤訊息才會出現。
Comments