如果想要能夠在子層繼承並覆寫 initialize, execute 等等前面已設定好的 callbacks keyword methods,並同時讓 run_callbacks
的 block 依舊能夠包覆整個 method,我們需要來做點 tricky 的事情。
這邊用的手法是,當建立物件後,把物件的原 instance method 給 hack 掉,幫他裝上 run_callbacks
。
因為我們在前篇規劃的 callbacks 有把 initialize 也一起規劃進去,所以要 hack 掉 initialize 就要把 .new
這個 method 給拆開來看。
首先要知道,當一個 class 在 new 一個 object 的時候,一定會經過以下步驟:
object = self.allocate # class method, 用以建立一個空白物件
object.send(:initialize, params) # 傳入參數以執行物件的 initialize
object # 回傳建好的物件
所以要 hack 掉 initialize 的最好時機點就是在 allocate
之後,在 initialize
之前。
object = self.allocate
# singleton_class 是一個介於實體與類別中間的抽象層,改動他的定義只會對該獨立實體造成影響。
object.singleton_class.class_eval do
# 先把原本物件不帶 run_callbacks 版本的 method 給備份起來
alias_method :initialize_without_callbacks, :initialize
# 在 run_callbacks 裡面再去執行原本的 initialize
def initialize(params={})
run_callbacks(:initialize) do
initialize_without_callbacks(params)
end
end
end
object.send(:initialize, params)
object
這樣做的好處是,當你繼承的時候,繼承的是不含 callback 的 method,可以放心做覆寫,且建立出的物件在執行時都會觸發 callbacks ,前篇提到的難點就都解決了,皆大歡喜!
沿用前一篇的 class,寫起來像這樣:
class PlaceOrder
include ActiveModel::Model
define_model_callbacks :initialize, :execute
# 到目前為止跟前篇都一樣,但不同的是,這邊我們要把 run_callbacks 在建立物件階段從 class 層掛上去。
def self.build(params={}) # 盡量避免去覆寫掉原生的 `.new` method
object = self.allocate
object.singleton_class.class_eval do
alias_method :initialize_without_callbacks, :initialize
def initialize(params={})
run_callbacks(:initialize) do
initialize_without_callbacks(params)
end
end
end
object.send(:initialize, params)
object
end
end
但我除了 initialze
之外,還有 execute
的 callback 也要裝,怎麼辦?
不必寫兩遍,我們可以用 array 去把 callbacks 給 loop 出來,並改用 define_singleton_method
來做:
object = self.allocate
[:initialize, :execute].each do |callback_method|
origin_method = :"#{callback_method}_without_callbacks"
object.singleton_class.alias_method origin_method, callback_method
# define_singleton_method 可以單獨定義該物件的方法,用在實體就是實體方法,用在 class 就是類別方法
object.define_singleton_method(callback_method) do |*args|
run_callbacks(callback_method) do
send(origin_method, *args)
end
end
end
object.send(:initialize, params)
object
這樣一來,既可以保有子層覆寫的彈性,又可以使物件在使用時能夠順利觸發 callbacks ,皆大歡喜!
如果想測試的話,我這篇有樣本可以使用,歡迎讀者您來測試看看!