我遇到了3.1.0.rc4上的Rails.cache方法的问题(ruby 1.9.2p180(2011-02-18修订版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)上的相同应用程序中正常工作,但在升级后开始返回错误。我还没有弄明白为什么。
当尝试缓存具有多个范围的对象时,似乎会发生错误。
此外,无论使用多少范围,使用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 =&gt; true选项作为替代方案,但由于Rails.cache.fetch块正在尝试缓存对象,因此无法正常工作。
有什么建议吗?提前谢谢!
答案 0 :(得分:98)
这可能有点冗长,但我不得不花一些时间使用Rails源代码来了解缓存内部的工作原理。写下来有助于我的理解,我认为分享关于事情如何运作的一些注释不会受到伤害。如果你赶时间,请跳到最后。
这是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
内找到它,你会发现它使用Marshal将对象序列化为字节字符串,然后再进入缓存,然后再使用Marshal来反序列化对象。压缩问题在这里并不重要,重要的是使用Marshal。
问题is that:
无法转储某些对象:如果要转储的对象包括绑定,过程或方法对象,类IO实例或单例对象,则会引发TypeError。
某些东西具有Marshal无法序列化的状态(例如OS文件描述符或块)。您注意到的错误是:
无法使用默认proc
转储哈希
因此,模型中的某个人拥有一个Hash实例变量,而Hash使用一个块来提供默认值。 column_methods_hash
方法使用这样的哈希,甚至在@dynamic_methods_hash
内缓存哈希; column_methods_hash
将通过respond_to?
和method_missing
等公共方法(间接)调用。{/ 1}
respond_to?
或method_missing
中的一个可能迟早会在每个AR模型实例上调用,并且调用任一方法都会使您的对象不可序列化。因此,AR模型实例在Rails 3中基本上是不可序列化的。
有趣的是,2.3.8中的respond_to?
和method_missing
实现也由一个使用块作为默认值的Hash支持。 2.3.8缓存是"[...]is meant for caching strings."所以你很幸运,后端可以处理整个对象,或者在你的对象中有hash-with-proc之前使用了Marshal;或者你可能正在使用MemoryStore
缓存后端,这只不过是一个大哈希。
使用多个scope-with-lambdas可能最终将Procs存储在AR对象中;我希望lambdas与类(或单例类)而不是对象一起存储,但我没有打扰分析,因为respond_to?
和method_missing
的问题使得{{1问题无关紧要。
我认为你已经将错误的东西存储在缓存中并且变得幸运。您可以正确地开始使用Rails缓存(即存储简单生成的数据而不是整个模型),也可以实现scope
/ marshal_dump
或marshal_load
/ _dump
方法在Marshal中概述。或者,您可以使用其中一个MemoryStore后端,并将自己限制为每个服务器进程一个不同的缓存。
除非您已准备好自己处理编组,或者希望将自己限制在MemoryStore缓存后端,否则不能依赖于在Rails缓存中存储ActiveRecord模型对象。
答案 1 :(得分:5)
多亏了他的出色分析。我现在设法让我的模型序列化:
def marshal_dump
{}.merge(attributes)
end
def marshal_load stuff
send :initialize, stuff, :without_protection => true
end
我还有一些&#34;虚拟属性&#34;使用AS
通过直接SQL连接查询设置,例如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
答案 2 :(得分:3)
我意识到使用where或某些范围创建了ActiveRecord::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
答案 3 :(得分:0)
完成更改后,只需删除默认的proc。类似的东西:
your_hash.default = nil # clear the default_proc