Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to programmatically remove "singleton information" on an instance to make it marshal?

I have created an object that failed to marshal due to a "singleton metaclass definition executed on runtime" (Is this description of what the code does correct?).

This is performed by following code:

# define class X that my use singleton class metaprogramming features
# through call of method :break_marshalling!
class X
   def break_marshalling!
     meta_class = class << self
       self 
     end
     meta_class.send(:define_method, :method_y) do 
      return 
    end
  end
end

# prepare my instance of X now
instance_of_x = X.new

# marshalling fine here
Marshal.dump instance_of_x

# break marshalling with metprogramming features
instance_of_x.break_marshalling!

Marshal.dump instance_of_x
# fails with TypeError: singleton can't be dumped 

What can I do to make the object marshall correct? Is it possible to "remove" the singleton components from class X of object instance_of_x?

I really need an advise on that because of some of our objects needed to be cached through Marshal.dump serialization mechanism. This code is executed in ruby-1.9.3 but I expect it to behave similar in ruby-2.0 or ruby-2.1

like image 396
criess Avatar asked May 19 '14 09:05

criess


2 Answers

The one-liner way to (usually) remove singleton information from instance_of_x:

instance_of_x = instance_of_x.dup
like image 169
histocrat Avatar answered Sep 28 '22 15:09

histocrat


You can define custom marshal_dump and marshal_load methods:

class X
  def break_marshalling!
    meta_class = class << self
      self 
    end
    meta_class.send(:define_method, :method_y) do 
      return 
    end
  end

  # This should return an array of instance variables
  # needed to correctly restore any X instance. Assuming none is needed
  def marshal_dump
    []
  end

  # This should define instance variables
  # needed to correctly restore any X instance. Assuming none is needed
  def marshal_load(_)
    []
  end
end

# Works fine
restored_instance_of_x = 
  Marshal.load Marshal.dump(X.new.tap { |x| x.break_marshalling! })

# Does not work
restored_instance_of_x.method_y

If you want you can manage dynamic methods definitions via method_missing:

class X
  def method_missing(name, *args)
    if name == :method_y
      break_marshalling!
      public_send name, *args
    else
      super
    end
  end
end

# Works fine
Marshal.load(Marshal.dump(X.new)).method_y
like image 22
mdesantis Avatar answered Sep 28 '22 14:09

mdesantis