Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

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

#5002

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"}>>

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

State: invalid
Milestone: none
Assigned to: Aaron Patterson Aaron Patterson
Importance: Low

Activity

  1. Stefan Siebel
    Stefan Siebel
    • Title changed from Nested objects don't deserialized completely (Rails 3 beta 4) to Nested objects don't deserialize completely (Rails 3 beta 4)

    June 29th, 2010 @ 12:00 PM

  2. Aaron Patterson
    Aaron Patterson
    • 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.

    June 30th, 2010 @ 12:45 AM

  3. Stefan Siebel
    Stefan Siebel

    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

    June 30th, 2010 @ 09:01 PM

  4. Aaron Patterson
    Aaron Patterson
    • 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!

    June 30th, 2010 @ 11:50 PM

  5. Stefan Siebel
    Stefan Siebel

    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.

    July 1st, 2010 @ 09:45 AM