This project is archived and is in readonly mode.

#5002 ✓invalid
Stefan Siebel

Nested objects don't deserialize completely (Rails 3 beta 4)

Reported by Stefan Siebel | June 29th, 2010 @ 11:55 AM

I tried to save a nested object structure with the ActiveRecord serialize method. These are the classes:

class PhotoStyle
  attr_accessor :image
 
  def initialize
    @image = StyleElement.new
  end
end
 
class StyleElement
  attr_accessor :selector, :style
 
  def initialize
    @selector = '*'
    @style = {}
  end
end

The model:

class Photo < ActiveRecord::Base
  serialize :style, PhotoStyle
end
When I save a Photo instance, and load it again, the style looks like this:
p = Photo.last
p.style
=> #<photostyle :0xb70a906c @image=#<YAML::Object:0xb72079a4 @ivars={"selector"=>"#theimage", "style"=>{"border"=>"0", "box-shadow"=>"1px 3px 15px #555"}}, @class="StyleElement">}, @class="PhotoStyle">>
The StyleElement doesn't get deserialized.

I'm not sure if that's by design or a bug.

I was able to "fix" this by adding below code to an initializer of my rails3 beta 4 app:


YAML::Syck::Resolver.class_eval do
  def transfer_with_autoload(type, val)
    match = type.match(/object:(\w+(?:::\w+)*)/)
    match && match[1].constantize
    transfer_without_autoload(type, val)
  end
  alias_method_chain :transfer, :autoload
end

After restarting the app deserialization works fine:

#<PhotoStyle :0xb73dcb08 @image=#<StyleElement:0xb73dd058 @selector="#theimage", @style={"border"=>"0", "box-shadow"=>"1px 3px 15px #555"}>>

Comments and changes to this ticket

  • Stefan Siebel

    Stefan Siebel June 29th, 2010 @ 12:00 PM

    • Title changed from “Nested objects don't deserialized completely (Rails 3 beta 4)” to “Nested objects don't deserialize completely (Rails 3 beta 4)”
  • Aaron Patterson

    Aaron Patterson June 30th, 2010 @ 12:45 AM

    • Assigned user set to “Aaron Patterson”
    • Importance changed from “” to “Low”

    Could you possibly upload an app that has this error (and a test)? I'd like to take a look.

  • Stefan Siebel

    Stefan Siebel June 30th, 2010 @ 09:01 PM

    Hi Aaron,

    cool, thank you! Please see attached file. It looks like the issue is a little bit weird. I tried to reproduce it with a test, but apparently the test works fine.

    That's how you can reproduce the issue:

    1) Create a new user object through the UI (localhost:3000/users)
    2) load the rails console, type in:

    u = User.last
    u.preferences.test.kind_of? Something
    => false
    

    Another to reproduce it, only with rails console, without web UI:

    
    a = User.new
    a.save
    
    b = User.last
    b.preferences.test.kind_of? Something
    => true
    
    reload!
    
    b = User.last
    b.preferences.test.kind_of? Something
    => false
    

    Does that help?

    -Stefan

  • Aaron Patterson

    Aaron Patterson June 30th, 2010 @ 11:50 PM

    • Tag changed from activerecord rails3, serialize to activerecord rails3, serialize, yaml
    • State changed from “new” to “invalid”

    Hey Stefan!

    Thanks for the sample application! I was indeed able to reproduce the problem, but unfortunately the problem isn't from Rails. I'll show you the fix for your code, then go in to detail about the problem.

    Just add this to the top of your photo_style.rb file:

    require 'style_element'
    

    The problem is that your class isn't defined at the time the YAML gets deserialized. You've taught Rails how to load the top level constant when you made the serialize :preferences, UserPreferences call. Unfortunately, it doesn't know how to load the other class. We can reproduce this behavior with a Ruby program like this:

    require 'yaml'
    
    class UserPreferences
    end
    
    p YAML.load DATA.read
    
    __END__
    --- !ruby/object:UserPreferences 
    test: !ruby/object:Something 
      a: 1
      b: 2
      c: 3
    

    You'll note that the output of this shows the same problem that the rails code is experiencing. If you were using Marshal to serialize and deserialize these objects, this case would actually result in an exception being raised. The reason an exception is raised with marshal is because it doesn't know the definition of that object, so it can't properly deserialize it.

    I believe this difference between Marshal and YAML to be a bug. The newer YAML parser in ruby 1.9 (psych) will raise an exception in this case, matching the behavior of Marshal. For example:

    require 'yaml'
    
    YAML::ENGINE.yamler = 'psych'
    
    class UserPreferences
    end
    
    p YAML.load DATA.read
    
    __END__
    --- !ruby/object:UserPreferences 
    test: !ruby/object:Something 
      a: 1
      b: 2
      c: 3
    

    If you run the above code using Ruby 1.9.2 you'll see the exception raised. That exception can give rails a chance to load the missing constant.

    I hope this explanation helps!

  • Stefan Siebel

    Stefan Siebel July 1st, 2010 @ 09:45 AM

    Excellent Aaron! Thank you!

    I already sort of expected it's something I'm doing wrong since I'm still pretty new to Ruby / Rails :-)

    Thanks again, I really appreciate!

    -Stefan.

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Attachments

Referenced by

Pages