Rails不会在反序列化YAML / Marshal对象时加载类
- Rails:3.0.3
- Ruby:1.9.2
尝试使用YAML.load
或Marshal.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]的类,因此在开发环境中,类自动刷新将保持工作。
优雅的解决方
修补YAML和Marshal 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自动加载器。 您必须继续并预加载可能需要反序列化的任何类。
这有点大惊小怪,特别是在开发环境中,在需要之前几乎没有任何东西被加载。