Rails 3.1的Rails.cache错误 – TypeError:不能用默认的proc转储hash

我遇到了3.1.0.rc4上Rails.cache方法的问题(ruby 1.9.2p180(2011-02-18 revision 30909)[x86_64-darwin10])。 在2.3.12(ruby 1.8.7(2011-02-18 patchlevel 334)[i686-linux],MBARI 0x8770,Ruby Enterprise Edition 2011.03)中,该代码工作正常,但是在升级之后开始返回一个错误。 我还没有弄清楚为什么。

尝试caching具有多个作用域的对象时,似乎发生该错误。

另外,不pipe有多less个作用域,任何使用lambdas的作用域都会失败。

我从这些模式中失败了:

Rails.cache.fetch("keyname", :expires_in => 1.minute) do Model.scope_with_lambda end Rails.cache.fetch("keyname", :expires_in => 1.minute) do Model.scope.scope end 

这是我收到的错误:

 TypeError: can't dump hash with default proc from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:627:in `dump' from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:627:in `should_compress?' from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:559:in `initialize' from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:363:in `new' from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:363:in `block in write' from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:520:in `instrument' from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:362:in `write' from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:299:in `fetch' from (irb):62 from /project/shared/bundled_gems/ruby/1.9.1/gems/railties-3.1.0.rc4/lib/rails/commands/console.rb:45:in `start' from /project/shared/bundled_gems/ruby/1.9.1/gems/railties-3.1.0.rc4/lib/rails/commands/console.rb:8:in `start' from /project/shared/bundled_gems/ruby/1.9.1/gems/railties-3.1.0.rc4/lib/rails/commands.rb:40:in `<top (required)>' from script/rails:6:in `require' from script/rails:6:in `<main>' 

我已经尝试使用:raw => true选项作为替代,但是这是行不通的,因为Rails.cache.fetch块试图caching对象。

有什么build议么? 提前致谢!

这可能有点冗长,但我不得不花一些时间与Rails源代码学习如何caching内部工作。 把事情写下来有助于我的理解,我认为分享一些关于事情如何运作的笔记不能伤害。 如果你急着跳到最后。


为什么会发生

这是ActiveSupport中的违规方法:

 def should_compress?(value, options) if options[:compress] && value unless value.is_a?(Numeric) compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT serialized_value = value.is_a?(String) ? value : Marshal.dump(value) return true if serialized_value.size >= compress_threshold end end false end 

请注意对serialized_value的分配。 如果你在cache.rb里面cache.rb ,你会发现它使用Marshal将对象序列化为字节string,然后再进入高速caching,然后重新cache.rb来反序列化对象。 压缩问题在这里并不重要,重要的是使用元帅。

问题是 :

有些对象不能被转储:如果要转储的对象包含绑定,过程或方法对象,类IO实例或单例对象,则会引发TypeError。

有些东西有状态(如操作​​系统文件描述符或块),不能由元帅序列化。 你注意到的错误是这样的:

不能使用默认的proc转储散列

所以你模型中的某个人有一个实例variables,它是一个哈希,哈希使用一个块来提供默认值。 column_methods_hash方法使用这样一个哈希,甚至在@dynamic_methods_hash里caching哈希; column_methods_hash将通过诸如respond_to?类的公共方法被间接respond_to?method_missing

其中之一是respond_to?method_missing可能迟早会在​​每个AR模型实例上调用,并调用任一方法使得您的对象是不可序列化的。 所以,AR模型实例在Rails 3中基本上是不可序列化的。

有趣的是, respond_to? 和2.3.8中的method_missing实现也由一个使用默认值块的Hash支持。 2.3.8caching是“[…]用于cachingstring”。 所以你很幸运,可以处理整个对象的后端,或者在你的对象中包含hash-with-procs之前使用Marshal; 或者也许你正在使用MemoryStorecaching后端,这只不过是一个大的哈希。

使用多个scope-with-lambdas可能会最终在您的AR对象中存储Procs; 我希望lambda存储与类(或单身类),而不是对象,但我不打扰与respond_to?问题的分析respond_to?method_missing使scope问题无关紧要。

你可以做些什么

我认为你一直在caching中存储错误的东西,幸运的。 您可以开始正确使用Railscaching(即存储简单的生成数据而不是整个模型),也可以实现Marshal中概述的marshal_dump / marshal_load_dump / _load方法。 或者,您可以使用其中一个MemoryStore后端,并将每个服务器进程限制为一个独立的caching。


执行摘要

除非你准备好自己处理编组,否则你不能依赖于在Railscaching中存储ActiveRecord模型对象,或者你想限制自己到MemoryStorecaching后端。

由于他的出色的分析,他是太短了。 我设法让我的模型现在序列化:

 def marshal_dump {}.merge(attributes) end def marshal_load stuff send :initialize, stuff, :without_protection => true end 

我也有一些“虚拟属性”由直接SQL连接查询使用AS例如SELECT DISTINCT posts.*, name from authors AS author_name FROM posts INNER JOIN authors ON author.post_id = posts.id WHERE posts.id = 123 。 对于这些工作,我需要为每个声明一个attr_accessor ,然后转储/加载它们,如下所示:

 VIRTUAL_ATTRIBUTES = [:author_name] attr_accessor *VIRTUAL_ATTRIBUTES def marshal_dump virtual_attributes = Hash[VIRTUAL_ATTRIBUTES.map {|col| [col, self.send(col)] }] {}.with_indifferent_access.merge(attributes).merge(virtual_attributes) end def marshal_load stuff stuff = stuff.with_indifferent_access send :initialize, stuff, :without_protection => true VIRTUAL_ATTRIBUTES.each do |attribute| self.send("#{attribute}=", stuff[attribute]) end end 

使用Rails 3.2.18

我意识到使用哪里或某些作用域创buildActiveRecord::Relation对象。 然后我注意到,做一个简单的Model.find工作。 我怀疑它不喜欢ActiveRecord::Relation对象,所以我强制转换为一个普通的Array ,并为我工作。

 Rails.cache.fetch([self.id, 'relA']) do relA.where( attr1: 'some_value' ).order( 'attr2 DESC' ).includes( :rel_1, :rel_2 ).decorate.to_a end 

完成更改后,只需删除默认的proc。 就像是:

 your_hash.default = nil # clear the default_proc