Participating in the Monitoring Process
參與監控過程
ActiveRecord控制著model對象 的生命周期,它創建它們,在修改,保存和更新的時候監控它們,並且在刪除的時候也進行監控。使用回調函 數,ActiveRecord允許我們的代碼參與這個監控過程。
ActiveRecord總共定義了20個回調函數。18個 成對的before和after,還有兩個例外:after_find和after_initialize。
實現callback有兩種方式。
第一種,直接在對象的回調方法中寫代碼。
class Order < ActiveRecord::Base def after_save self.payment_due ||= Time.now + 30.days end end
第二種,為回調聲明一個處理器,處理器可以是一個方法,或者是一個block。
class Order < ActiveRecord::Base before_validation :normalize_credit_card_number after_create do |order| logger.info "Order #{order.id} created" end protected def normalize_credit_card_number self.cc_number.gsub!(/[-\s]/, '') end end
你可以為一個回調函數指定多個處理程序,多個處理程序會按照指定的順序執行,除非其中一個處理程序 返回false,這時候才會終止後面的處理程序。
因為需要優化性能,定義after_find和 after_initialize只能用方法的方式,如果使用其他方式,定義的處理程序會被忽略。
Grouping Related Callbacks Together
callback分組
可以將相關的callback處理方法定義在單獨的類中 ,這樣這些處理方法就可以在多個model中共享。一個處理類就是在一個類中定義回調方法,把這些類放在 app/models文件夾中。
class CreditCardCallbacks def before_validation(model) model.cc_number.gsub!(/[-\s]/, '') end end class Order < ActiveRecord::Base before_validation CreditCardCallbacks.new end class Subscription < ActiveRecord::Base before_validation CreditCardCallbacks.new end
上面的CreditCardCallbacks的before_validation就是共享的,這需要Order和Subscription都包含 cc_number屬性。共享的處理程序,需要處理相同的屬性,肯定需要共享處理程序的model有相同名稱的屬性。
我們可以定義一個加密和解密的處理程序。可以在存入數據庫之前對數據加密,從數據庫取出來之後 再進行解密。
class Encrypter def initialize(attrs_to_manage) @attrs_to_manage = attrs_to_manage end def before_save(model) @attrs_to_manage.each do |field| model[field].tr!("a-Z", "b-za") end end def after_save(model) @attrs_to_manage.each do |field| model[field].tr!("b-za", "a-Z") end end alias_method :after_find, :after_save end
require "encrypter" class Order < ActiveRecord::Base encrypter = Encrypter.new([:name, :email]) before_save encrypter after_save encrypter after_find encrypter protected def after_find end end
我們看到在上面的類中定義了空的after_find方法。前面我們說過after_find和 after_initialize的特殊性,這種特殊處理的後果就是ActiveRecord不知道需要調用after_find和 after_initialize,除非在model類中存在一個after_find和after_initialize的定義。所以說需要在model中 定義一個空的after_find方法。
但是需要用到這個加解密處理的model都需要添加上面的8行代碼,我 們可以做得更好。擴展一下ActiveRecord::Base類。
class ActiveRecord::Base def self.encrypt(*attr_names) encrypter = Encrypter.new(attr_names) before_save encrypter after_save encrypter after_find encrypter defind_method(:after_find) { } end end class Order < ActiveRecord::Base encrypt(:name, :email) end o = Order.new o.name = "swb" o.address = "sdfsf" o.email = "asdasdf" o.save puts o.name o = Order.find(o.id) puts o.name
callback是一個很好的技術,但是有時候濫用的話,在model中會產生一些和model不太 相關功能。例如在after_save中寫日志這樣的功能。
ActiveRecord的觀察者observer可以克服這些限 制。
Observers觀察者
ActiveRecord的observer是一個對象,可以透明的和model類進行連接, 在model中注冊自己,但是不需要修改model的任何代碼。
class OrderObserver < ActiveRecord::Observer def after_save(an_order) an_order.logger.info("Order #{an_order.id} created") end end
上面的這個observer會自動的注冊到Order這個model,因為rails有這方面的約定。
有時 候也會打破這個約定,可以在observer類中指定需要觀察的model。
class AuditObserver < ActiveRecord::Observer observe Order, Payment, Refund def after_save(model) model.logger.info("[Audit] #{model.class.name} #{model.id} created") end end
按照約定,observer類應該放在app/models文件夾中。
Instantiating Observers
實例化觀察者
觀察者需要實例化,如果不實例化它們,就不會激活它們。
如 果在rails應用中使用觀察者,你就需要在config/environment.rb文件中設置。
config.active_record.observers = :order_observer, :audit_observer
如果在單獨的應用 中使用ActiveRecord對象,你需要手動創建實例。
OrderObserver.instance
AuditObserver.instance
在某種程度上,observers給rails的面向方面編程(Aspect-oriented Programming AOP)帶來了很多的好處。允許我們在不改變model代碼的同時,給model注入一些行為。