Rails doesn't load classes on deserializing YAML/Marshal objects
classloader, deserialization, ruby-on-rails
Solution
Well, after read @tadman and a bunch of answers I have received in the spanish ror mailing list [1] I have collected a few hot tips when you have to deal with Ruby deserializing and class loading in Rails:
Super fast solution
Use `config.cache_classes = true` in your `development.rb` but you will lost the class auto-refreshing.
Better solution
Require all the classes that are gonna be deserialized but not with `require` but with `require_dependency`[2] so in development environment the class auto-refreshing will remain working.
Elegant solution
Monkey-patch the YAML and the Marshal gem to tell them to call `require_dependency` when they find a non-defined class to deserialize.
And @Xavi has sent me a proposition of monkey-patch `Marshal` (he says he wrote it on the air and it is not tested so use it in your own risk) [3]
- [1] http://lists.simplelogica.net/pipermail/ror-es/2011-January/024787.html
- [2] http://apidock.com/rails/ActiveSupport/Dependencies/Loadable/require_dependency
- [3] http://lists.simplelogica.net/pipermail/ror-es/2011-January/024796.html
Problem
- Rails: 3.0.3 - Ruby: 1.9.2 Trying to deserialize a very simple object using `YAML.load` or `Marshal.load` produces a corrupted object because the class which belongs to is not required on the deserializing process. Example: ``` # 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 ``` With this code we can run this shell console session without any error: ``` $ ruby -Itest test/unit/serializing_test.rb -n test_yaml_serialize_structure $ ruby -Itest test/unit/serializing_test.rb -n test_yaml_deserialize_structure ``` But if I run the deserialization calls from a Rails console the object is not deserialized properly because the class is never required: ``` $ 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"}> ``` I know the only problem is that the class is not required because if I require it by hand everything works: ``` 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"> ``` I have presented only the YAML examples but with Marshal is pretty the same. Also say that although I'm reproducing the problem in a Rails console originally this problem was turning me crazy in a normal request to my application. So the question is: How can I deserialize objects in Rails without have to require all my classes by hand? Thanks f.