iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
Software Development

Rails Active Model系列 第 26

D-26 如何把 run_callbacks 藏起來?

  • 分享至 

  • xImage
  •  

如果想要能夠在子層繼承並覆寫 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 ,皆大歡喜!
如果想測試的話,我這篇有樣本可以使用,歡迎讀者您來測試看看!


上一篇
D-25 該如何規劃 form object 的 life cycle ?
下一篇
D-27 合理規劃 validation 規則
系列文
Rails Active Model28
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言