参与监控过程
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注入一些行为。
本文转自 virusswb 51CTO博客,原文链接:http://blog.51cto.com/virusswb/1016391,如需转载请自行联系原作者