41 votes

Erreur Rails.cache dans Rails 3.1 - TypeError: impossible de vider le hachage avec proc

J'ai en cours d'exécution dans un problème avec les Rails.cache méthodes sur 3.1.0.rc4 (ruby 1.9.2p180 (2011-02-18 révision 30909) [x86_64-darwin10]). Le code fonctionne très bien au sein de la même application sur 2.3.12 (ruby 1.8.7 (2011-02-18 version 334) [i686-linux], MBARI 0x8770, Ruby Enterprise Edition 2011.03), mais a commencé à retourner une erreur après la mise à niveau. Je n'ai pas été en mesure de comprendre pourquoi encore.

L'erreur semble se produire lorsque vous essayez d'en cache les objets qui ont plus d'une portée sur eux.

Aussi, toutes portées à l'aide de lambda échouer indépendamment de la façon dont de nombreux champs d'application.

J'ai frappé des échecs de ces modèles:

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

C'est l'erreur que je reçois:

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>'

J'ai essayé d'utiliser l' :raw => true option comme une alternative, mais qui ne fonctionne pas parce que les Rails.le cache.extraction de blocs de tenter de cache d'objets.

Toutes les suggestions? Merci à l'avance!

100voto

mu is too short Points 205090

Cela pourrait être un peu verbeux, mais j'ai dû passer un peu de temps avec les Rails de code source pour savoir comment la mise en cache des éléments internes de travail. D'écrire des choses sida ma compréhension et je me dis que le partage de quelques notes sur la façon dont les choses fonctionnent peut pas faire de mal. Sauter à la fin, si vous êtes pressé.


Pourquoi Ça Arrive

C'est de la délinquance de la méthode à l'intérieur d'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

Remarque l'affectation d' serialized_value. Si vous fouiller à l'intérieur d' cache.rb, vous verrez qu'il utilise le Maréchal de sérialiser des objets de chaînes d'octets avant d'aller dans le cache, puis Maréchal de nouveau pour désérialiser des objets. La compression problème n'est pas important, l'important c'est l'utilisation du Maréchal.

Le problème est que:

Certains objets ne peuvent pas être sous-évaluées: si les objets à être sous-évaluées inclure des liaisons, de la procédure ou la méthode des objets, instances de la classe IO, ou les objets singleton, une erreur TypeError sera soulevée.

Certaines choses ont l'état (comme les OS des descripteurs de fichiers ou blocs) ne peut pas être sérialisé par le Maréchal. L'erreur que vous avez noté, c'est ceci:

peut pas jeter de hachage par défaut proc

Si quelqu'un dans votre modèle dispose d'une variable d'instance qui est un Hachage et que de Hachage utilise un bloc de fournir des valeurs par défaut. L' column_methods_hash méthode utilise ce Hachage et même les caches à l'intérieur de la table de Hachage @dynamic_methods_hash; column_methods_hash sera appelé (indirectement) par des méthodes publiques telles que l' respond_to? et method_missing.

L'un d' respond_to? ou method_missing sera probablement appelé à chaque modèle AR exemple, tôt ou tard, et l'appel de la méthode fait l'objet de votre unserializable. Ainsi, le modèle AR instances sont essentiellement unserializable dans Rails 3.

Curieusement, l' respond_to? et method_missing des implémentations dans 2.3.8 sont également garantis par une table de Hachage qui utilise un bloc de valeurs par défaut. Le 2.3.8 cache est "[...]est conçu pour la mise en cache des chaînes." si vous avez été chanceux avec un backend qui pourrait gérer l'ensemble des objets ou elle a utilisé le Maréchal avant de vos objets de hachage-avec-procs en eux; ou peut-être vous ont été à l'aide de l' MemoryStore cache arrière-plan et c'est un peu plus qu'un gros Hachage.

À l'aide de plusieurs champ d'application-avec-des lambdas pourrait stocker des Procs dans votre AR objets; je m'attends à de la lambdas être stockée avec la classe (ou classe singleton) plutôt que les objets, mais je n'ai pas pris la peine avec une analyse un problème avec respond_to? et method_missing fait l' scope question non pertinente.

Ce Que Vous Pouvez Faire À Ce Sujet

Je pense que vous avez été stocker les mauvaises choses dans votre cache et faire de la chance. Vous pouvez commencer à utiliser les Rails cache correctement (c'est à dire stocker simple généré des données plutôt que l'ensemble des modèles) ou vous pouvez mettre en œuvre l' marshal_dump/marshal_load ou _dump/_load méthodes décrites dans Maréchal. Alternativement, vous pouvez utiliser l'une des MemoryStore backends et vous limiter à une seule cache par le serveur de processus.


Résumé

Vous ne pouvez pas dépendre sur le stockage de ActiveRecord les objets de modèle dans les Rails cache, sauf si vous êtes prêt à gérer les gares de vous-même ou que vous voulez vous limiter à la MemoryStore cache backends.

5voto

AlexChaffee Points 2979

Merci à mu-is-too-short pour son excellente analyse. J'ai réussi à faire sérialiser mon modèle maintenant avec ceci:

 def marshal_dump
  {}.merge(attributes)
end

def marshal_load stuff
  send :initialize, stuff, :without_protection => true
end
 

J'ai également des "attributs virtuels" définis par une requête de jointure SQL directe utilisant AS par exemple SELECT DISTINCT posts.*, name from authors AS author_name FROM posts INNER JOIN authors ON author.post_id = posts.id WHERE posts.id = 123 . Pour que cela fonctionne, je dois déclarer attr_accessor pour chaque, puis les vider / charger aussi, comme suit:

 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
 

Utiliser Rails 3.2.18

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X