11
  • 导轨:3.0.3
  • 红宝石: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"
 => #<Syck::Object:0x0000010322ea30 @class="MyModel", @ivars={"id"=>"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"
 => #<MyModel:0x0000010320c8e0 @id="my model"> 

我只介绍了 YAML 示例,但与 Marshal 几乎相同。

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

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

谢谢

F。

4

5 回答 5

20

好吧,在阅读了@tadman和我在西班牙 ror 邮件列表 [1] 中收到的一堆答案之后,我收集了一些当您必须在 Rails 中处理 Ruby 反序列化和类加载时的热门技巧:

超快速解决方案

config.cache_classes = true在你的development.rb,但你会失去类自动刷新。

更好的解决方案

要求所有要反序列化的类,但不是使用 [2]require而是使用require_dependency[2],因此在开发环境中,类自动刷新将继续工作。

优雅的解决方案

对YAMLMarshal gem 进行猴子补丁,告诉他们require_dependency在找到要反序列化的未定义类时调用。

@Xavi我发了一个猴子补丁的提议Marshal(他说他是在广播中写的,没有经过测试,所以使用它需要您自担风险)[3]

于 2011-01-23T15:32:42.967 回答
2

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

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

在存储自定义类实例 YMMV 的序列化 Active Record 中为我工作。

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
于 2013-10-24T06:36:37.817 回答
2

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

于 2011-06-08T22:01:00.380 回答
1

我不得不稍微调整@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
于 2017-03-22T22:39:10.317 回答
0

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

这有点小题大做,尤其是在开发环境中,在需要之前几乎什么都没有加载。

于 2011-01-16T18:28:30.467 回答