Ошибка Rails.cache в Rails 3.1 - TypeError: не может сбрасывать хеш с обработкой по умолчанию
У меня возникла проблема с методами Rails.cache на 3.1.0.rc4 (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 = > true в качестве альтернативы, но это не работает, потому что блоки Rails.cache.fetch пытаются кэшировать объекты.
Любые предложения? Спасибо заранее!
Ответы
Ответ 1
Это может быть немного многословно, но мне пришлось потратить некоторое время с исходным кодом 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 для сериализации объектов в байтовые строки, прежде чем они попадут в кеш, а затем снова маршала для десериализации объектов. Проблема сжатия здесь не важна, важно использовать маршала.
Проблема в том, что:
Некоторые объекты не могут быть выгружены: если объекты, которые должны быть выгружены, включают в себя привязки, объекты процедур или методов, экземпляры класса IO или одноэлементные объекты, выдается ошибка TypeError.
Некоторые вещи имеют состояние (например, дескрипторы файлов ОС или блоки), которые не могут быть сериализованы маршалом. Вы заметили следующую ошибку:
не может вывести хэш с процедурой по умолчанию
Таким образом, кто-то в вашей модели имеет переменную экземпляра, которая является Hash, и этот Hash использует блок для предоставления значений по умолчанию. Метод column_methods_hash
использует такой хэш и даже кэширует хэш внутри @dynamic_methods_hash
; column_methods_hash
будет вызываться (косвенно) публичными методами, такими как respond_to?
и method_missing
.
Один из respond_to?
или method_missing
, вероятно, рано или поздно будет вызван на каждом экземпляре модели AR, и вызов любого из этих методов сделает ваш объект не сериализуемым. Таким образом, экземпляры AR-моделей практически не могут быть проанализированы в Rails 3.
Интересно, что реализации respond_to?
и method_missing
в 2.3.8 также поддерживаются хэшем, который использует блок для значений по умолчанию. Кеш 2.3.8 "[...] предназначен для кэширования строк." поэтому вам повезло с бэкэндом, который может обрабатывать целые объекты, или он использовал Marshal до того, как ваши объекты имели хэш-с- процы в них; или, возможно, вы использовали MemoryStore
бэкэнд кеша и это чуть больше, чем большой хэш.
Использование нескольких scope-with-lambdas может привести к хранению Procs в ваших объектах AR; Я ожидал, что лямбды будут храниться с классом (или синглтон-классом), а не с объектами, но я не стал заниматься анализом, поскольку проблема с respond_to?
и method_missing
делает проблему scope
неактуальной.
Что вы можете с этим сделать
Я думаю, что вы хранили неправильные вещи в своем кэше и вам повезло. Вы можете либо начать использовать кеш Rails должным образом (т.е. хранить простые сгенерированные данные, а не целые модели), либо вы можете реализовать методы marshal_dump
/marshal_load
или _dump
/_load
, как описано в Marshal. Кроме того, вы можете использовать один из бэкэндов MemoryStore и ограничить себя одним отдельным кешем для каждого процесса сервера.
Резюме
Вы не можете зависеть от хранения объектов модели ActiveRecord в кэше Rails, если вы не готовы самостоятельно выполнять маршалинг или не хотите ограничивать себя бэкэндами кэша MemoryStore.
Точный источник проблемы изменился в более поздних версиях Rails, но все еще есть много экземпляров default_proc
, связанных с хэшами.
Ответ 2
Благодаря му-слишком-короткому для его превосходного анализа. Мне удалось получить мою серию сериализации теперь с этим:
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
Ответ 3
Я понял, что использование того или другого объекта создало объекты 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
Ответ 4
просто удалите proc по умолчанию после его изменения. что-то вроде:
your_hash.default = nil # clear the default_proc