Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't dump anonymous class if default value not set. #47

Closed
gcrofils opened this issue Feb 12, 2018 · 9 comments
Closed

Can't dump anonymous class if default value not set. #47

gcrofils opened this issue Feb 12, 2018 · 9 comments

Comments

@gcrofils
Copy link

Summary

Marshal.dump fails on any object if default value is not defined for Jsonb attributes

Steps to replicate

  1. New Rails app rails new storext-demo --api
  2. Add pg gem, add storextgem
  3. create model with default value
  • rails g model book title:string data:jsonb
class Book < ApplicationRecord
  include Storext.model
  store_attributes :data do
    sync Boolean, default: true
    sync_at DateTime, default: 0
  end
end
  1. create model without default value
  • rails g model post title:string data:jsonb
class Post < ApplicationRecord
 include Storext.model
 
 store_attributes :data do
   sync Boolean
   sync_at DateTime
 end
 
end
  1. Marshal.dump works on model with default value
  • Book.create title:'Lorem Ipsum'
  • Marshal.dump Book.first ==> OK
  1. Marshal.dump fails on model without default value
  • Post.create title:'Lorem Ipsum'
  • Marshal.dump Post.first ==> KO TypeError: can't dump anonymous class #<Class:0x007fefcdab1340>
@ramontayag
Copy link
Contributor

ramontayag commented Feb 23, 2018

@gcrofils when writing, Storext just uses ActiveRecord::Store underneath. Are you able to do what you want with ActiveRecord::Store?

For example, if you create a stored attribute but the value is nil, what happens?

@gcrofils
Copy link
Author

gcrofils commented Feb 23, 2018

I don't fully understand your question "Are you able to do what you want with ActiveRecord::Store?".
In the previous example, if you remove Storext (include + store_attributes definition) from the class, without changing anything else. Marshal.dump works.

Regarding the second part of your message:

Marshal.dump fails if there is no value for an attribute with TypeError: can't dump anonymous class.
It works in other cases, either if there is a default value (defined in store_attributes) or a value set.
Hence if you try to Marshal.dump a record with a stored attribute that is nil, it will fail if this attribute has no default value.

@ramontayag
Copy link
Contributor

Ok that's right. Storext uses anonymous classes in which it defines Virtus attributes. The values go through an instance of that class and that instance that has Virtus does a lot of the typecasting/conversion. I'm not familiar with Marshal and don't really understand what it means when it says it will dump descendant objects.

@ramontayag
Copy link
Contributor

The fix might be somewhere here where marshal_dump and marshal_load are defined.

@topherfangio
Copy link

So, the issue lies here:

def storext_proxy_class
  Storext.proxy_classes[self] ||= Class.new(*Storext.proxy_classes[self.superclass]) do
    include Virtus.model
    attribute :source
  end
end

Marshal.dump just doesn't know how to dump the Class.new.

We were able to monkey patch this with the following code.

module Storext
  module ClassMethods
    class Dumpable
      include Virtus.model
      attribute :source
    end
    
    def storext_proxy_class
      Storext.proxy_classes[self] ||= Dumpable
    end
  end
end

But I'm pretty sure this exists to cover cases of a parent class that uses Storext as well as it's child class using Storext, so this could easily break stuff for us later on down the road. Not sure if there is another workaround, but we couldn't figure out how to use the Marshal methods to get it working.

@ramontayag
Copy link
Contributor

Thanks. This'll be helpful. Mind writing a failing test case? I'll see what I can do :)

@ramontayag
Copy link
Contributor

Right now all tests are failing in Travis, but you should be able to run it locally. I'm currently trying to fix the tests in Travis.

@seattlecyclist
Copy link

seattlecyclist commented Sep 19, 2018

@ramontayag - With regard to the error @topherfangio reported. I work with him. Here is a failing spec. Just drop it into your storext_spec.rb and run it.

  it "can properly marshal" do
    author = Author.new(name: "A. Clarke")
    expect{Marshal.dump(author)}.to_not raise_exception(TypeError)
  end

/edit Marshal.dump is used by memcache when you try to cache an object. So this use case is 'common' IMHO.

# this will fail.  
Rails.cache.fetch("a_user") do
   User.find(1)
end

Edit 2/ Docs on the Marshal.dump method https://ruby-doc.org/core-2.2.2/Marshal.html

ramontayag added a commit that referenced this issue Nov 28, 2018
@ramontayag
Copy link
Contributor

@seattlecyclist will #56 work for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants