Rails不会在反序列化YAML / Marshal对象时加载类

  • Rails:3.0.3
  • Ruby:1.9.2

尝试使用YAML.loadMarshal.load反序列化一个非常简单的对象会产生一个损坏的对象,因为在反序列化过程中不需要属于的类。

例:

 # app/models/my_model.rb class MyModel attr_accessor :id end # test/unit/serializing_test.rb require 'test_helper' class SerializingTest < Test::Unit::TestCase def test_yaml_serialize_structure my_model = MyModel.new my_model.id = 'my model' File.open( "#{Rails.root}/tmp/object.yml" , 'w' ) do |f| YAML::dump(my_model, f) end end def test_yaml_deserialize_structure object = YAML.load_file "#{Rails.root}/tmp/object.yml" assert( object.instance_of? MyModel ) assert_equal( 'my model', object.id ) end end 

使用此代码,我们可以运行此shell控制台会话而不会出现任何错误

 $ ruby -Itest test/unit/serializing_test.rb -n test_yaml_serialize_structure $ ruby -Itest test/unit/serializing_test.rb -n test_yaml_deserialize_structure 

但是如果我从Rails控制台运行反序列化调用 ,则对象不会被正确反序列化,因为该类永远不需要:

 $ rails c ruby-1.9.2-p0 > object = YAML.load_file "#{Rails.root}/tmp/object.yml" => #"my model"}> 

我知道唯一的问题是这个课程不是必需的,因为如果我手工需要它,一切都有效:

 ruby-1.9.2-p0 > require "#{Rails.root}/app/models/my_model" => ["MyModel"] ruby-1.9.2-p0 > object = YAML.load_file "#{Rails.root}/tmp/object.yml" => # 

我只提供了YAML的例子,但Marshal是相同的。

还要说虽然我在Rails控制台中重现了这个问题,但这个问题让我对我的应用程序的正常请求感到疯狂。

所以问题是: 如何在Rails中反序列化对象而不必手动需要我的所有类?

谢谢

F。

好吧,在阅读了@tadman和我在西class牙语邮件列表[1]中收到的一堆答案后,我已经收集了一些热门提示,当你必须处理Rails中的Ruby反序列化和类加载时:

超快的解决方案

development.rb使用config.cache_classes = true ,但是您将丢失类自动刷新。

更好的方案

要求所有要反序列化但不具有require但带有require_dependency [2]的类,因此在开发环境中,类自动刷新将保持工作。

优雅的解决方

修补YAMLMarshal gem,告诉他们在找到要反序列化的非定义类时调用require_dependency

@Xavi已经给我发了一个猴子补丁Marshal的命题(他说他是在空中写的,并没有经过测试所以请自行使用它)[3]

我在GitHub上描述了这个“问题”: https : //github.com/rails/rails/issues/1585

要以@fguillen建议的方式自动要求YAML加载类是优雅的,我写了这个简短的猴子补丁。

它只是尝试require_dependency Psych ToRuby类解析为类的任何类。

适用于我的序列化Active Record,它存储自定义类实例YMMV。

 module Psych::Visitors ToRuby.class_eval do alias :resolve_class_without_autoload :resolve_class def resolve_class klassname begin require_dependency klassname.underscore rescue NameError, LoadError end resolve_class_without_autoload klassname end end end 

我不得不调整@ ben-patterson的答案以使其工作(使用Rails 5.0.2):

 module Psych::Visitors ToRuby.class_eval do def resolve_class(klassname) begin class_loader.load klassname rescue ArgumentError require_dependency klassname.underscore klassname.constantize end end end end 

据我所知,YAML和Marshal都没有使用Rails自动加载器。 您必须继续并预加载可能需要反序列化的任何类。

这有点大惊小怪,特别是在开发环境中,在需要之前几乎没有任何东西被加载。