Rails не загружает классы при десериализации объектов YAML / Marshal - PullRequest
11 голосов
/ 16 января 2011
  • Рельсы: 3.0.3
  • Рубин: 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

С помощью этого кода мы можем запустить этот сеанс консоли оболочки без ошибок:

$ 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, но с маршалом все точно так же.

Также скажите, что хотя я воспроизводил проблему в консоли Rails , изначально эта проблема сводила меня с ума при обычном запросе к моему приложению.

Итак, вопрос в следующем: Как я могу десериализовать объекты в Rails без необходимости вручную запрашивать все мои классы?

Спасибо

е.

Ответы [ 5 ]

21 голосов
/ 23 января 2011

Что ж, после прочтения @ tadman и нескольких ответов, которые я получил в списке рассылки испанского языка [1], я собрал несколько полезных советов, когда вам нужно разобраться с десериализацией и загрузкой классов в Ruby. в рельсах:

Супер быстрое решение

Используйте config.cache_classes = true в вашем development.rb, но вы потеряете автообновление класса.

Лучшее решение

Требуются все классы, которые будут десериализованы, но не с require, а с require_dependency [2], поэтому в среде development автообновление классов будет работать.

Элегантное решение

Обезьяна-патч для драгоценного камня YAML и Marshal , чтобы сказать им, чтобы они вызывали require_dependency, когда они находят неопределенный класс для десериализации.

И @ Хави прислал мне предложение обезьяньего патча Marshal (он говорит, что написал его в эфире, и он не тестировался, поэтому используйте его на свой страх и риск) [3]

2 голосов
/ 24 октября 2013

Чтобы автоматически запрашивать классы при загрузке YAML в порядке, @ fguillen предполагает элегантность, я написал этот короткий патч-обезьяну.

Он просто пытается сделать require_dependency любым классом класса Psych ToRubyразрешается на классы.

Работает для меня в сериализованной активной записи, в которой хранится экземпляр пользовательского класса, 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
2 голосов
/ 09 июня 2011

Я описал эту «проблему» на GitHub: https://github.com/rails/rails/issues/1585

1 голос
/ 23 марта 2017

Мне пришлось немного адаптировать ответ @ 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
0 голосов
/ 16 января 2011

Насколько я знаю, и YAML, и Marshal не используют автозагрузчик Rails. Вы должны идти вперед и предварительно загружать любые классы, которые могут потребоваться для десериализации.

Немного суеты, особенно в среде разработки, где почти ничего не загружается, прежде чем это необходимо.

...