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

I created an object that could not be marshaled due to the "definition of the singleton metaclass executed at runtime" (is this a description of what the code does?).

This is done by the 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 marshal correct? Is it possible to "remove" singleton components from an class Xobject instance_of_x?

I really need some advice because some of our objects need to be cached through the Marshal.dump serialization mechanism. This code runs in ruby-1.9.3, but I expect it to behave similarly in ruby-2.0 or ruby-2.1

+4
4

marshal_dump marshal_load:

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

, 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
+3

() instance_of_x:

instance_of_x = instance_of_x.dup
+7

, remove_singleton_class_information!, singleton.

. , .

class Foo
  def break_marshal
    singleton_class.class_eval do
      @@x = true
      @x = false
      define_method :bar do
        puts @@x
        puts singleton_class.instance_variable_get(:@x)
        puts singleton_class.send(:baz)
        puts singleton_class.const_get(:X)
      end
      define_singleton_method :baz do
        "Singleton method in a singleton"
      end
    end
  end

  def fix_marshal
    singleton_methods.each { |m| singleton_class.send(:remove_method, m) }
    singleton_class.class_eval do
      instance_variables.each { |v| remove_instance_variable v }
    end
  end
end

f = Foo.new
f.break_marshal
f.singleton_class.const_set(:X, 1)
f.bar
f.fix_marshal

Marshal.dump f

, . singleton , , , singleton . , singleton .

However , I do not think this is the right way to marshal such an object. Answer @mdesantis to define your own marshal_dumpand marshal_loadmore correct.

0
source

I came up with this method that recursively removes singleton_methods from an object and from objects assigned to instance variables:

def self.strip_singleton(obj)
  obj = obj.dup unless (obj.nil? || obj.singleton_methods.empty?)
  obj.instance_variables.each do |var|
    obj.instance_variable_set(var, strip_singleton(obj.instance_variable_get(var)))
  end
  obj
end
0
source

Source: https://habr.com/ru/post/1541092/


All Articles