解决ActiveRecord经常要reload的一种方案
2013年4月22日 22:19
我发现写Rails,尤其是测试部分,需要reload的次数相当频繁。比方说
user = users(:xxxxx) xhr :put, 'change_xxxxx', :id => user.id user.reload # necessary! assert user.xxx, xxx assert user.yyy, yyy
由于这里创建的user对象和被测试的action里的user对象(在内存概念上)不是同一个对象,因此,被测试的action修改了user对象在数据库里的数据,会导致外围的user信息过时,因此必须reload,否则接下来的断言语句都无法通过。
这种情况同样也有可能存在与关联对象上,比如
assert child1.father, child2.father child1.father.name = 'new name' child1.save child2.reload # necessary! assert child2.father.name, 'new name'
总是写很多reload语句显然很不舒服,为此我今天在回家的地铁上想了一种解决方案,就是在后台全局的部分,以ActiveRecord类名和实例为key简单的缓存一部分数据,这部分数据与数据库里的数据在本次request返回内一致,request结束后自动清除以免内存泄露和影响下一个request。而前面的ActiveRecord对象,本身仅存储那些还未被保存的数据。
方案大致是这样的(以Rails 2.3为例):
require 'active_record' module ActiveRecord class Base def request_cache self.class.request_cache[self] end def request_cache=(v) self.class.request_cache[self] = v end end class << Base def request_cache @request_cache ||= Hash.new {|h, k| h[k] = Hash.new {|h, k| h[k] = {}}} @request_cache[name] end end end ActionController::Dispatcher.middleware.use(Class.new do def initializer(app) @app = app end def call(env) @app.call(env) ensure ActiveRecord::Base.request_cache.clear end end)
这里我把缓存就设计在ActiveRecord的class variable内(三级Hash,分别是类-实例对象-属性名),然后用Middleware的方式去清理缓存,之所以选择用Middleware的方式是因为ActiveRecord原本和HTTP Request是不能发生关联的,否则Console和Unit test里就无法执行了,设计成Middleware则能很好的解决这一问题。
由于选择的三级Hash的设计中没有直接和数据库关联的信息,因此其实也可以用于非ActiveRecord类(唯一要做的可能仅仅是自己实现一种标示方法),毕竟当前可以用于存储的服务早已不止数据库一种了。
至于对于ActiveRecord的修改我现在对ActiveRecord的实现还不够熟悉,无法完成,以后学习这一部分了再说吧。但是大致的思路是这样的:
- 当从数据库读入数据的时候(包括reload),将缓存里的数据翻新
- 当保存数据进数据库的时候,将数据同样保存进缓存
- 当从一个ActiveRecord对象读取属性的时候,现在被当前对象修改过的属性中搜索,如果搜索不到再从缓存里搜索,如果还是搜索不到就采用默认方法
大致就是这些了,其实想法还是很简单的,希望早日自己能把它变成现实。