Как да премахнете програмно информацията за сингълтън в екземпляр, за да го направите маршал?

Създадох обект, който не успя да се маршалира поради „дефиниция на единичен метаклас, изпълнена по време на изпълнение“ (правилно ли е това описание на това, което прави кодът?).

Това се извършва от следния код:

# 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 

Какво мога да направя, за да накарам обекта да се маршалира правилно? Възможно ли е да се „премахнат“ компонентите сингълтън от class X на обект instance_of_x?

Наистина се нуждая от съвет за това, тъй като някои от нашите обекти трябваше да бъдат кеширани чрез механизъм за сериализация Marshal.dump. Този код се изпълнява в ruby-1.9.3, но очаквам да се държи подобно в ruby-2.0 или ruby-2.1


person criess    schedule 19.05.2014    source източник
comment
Добър въпрос, но предизвикахте един от най-големите ми неприятности! Нарича се singleton_class. И не се нуждаем от този хак за изпращане с define_singleton_method наоколо.   -  person Max    schedule 20.05.2014
comment
И така, @Max попадал ли си на някакъв начин за програмно нулиране на singleton_class на екземпляр на който и да е ruby ​​клас? Нещо като remove_singleton_class_information! може би? ;)   -  person criess    schedule 21.05.2014


Отговори (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
person mdesantis    schedule 19.05.2014
comment
Имате предвид meta_class = class << self ...? Предполагам (и се надявам) питащият да има основателна причина да го използва =) - person mdesantis; 20.05.2014
comment
Някак си го забелязах в отговора ви, преди да го видя във въпроса. опа - person Max; 20.05.2014
comment
Първо благодаря за този отговор. Питащият всъщност не е използвал този метод за добавяне на сингълтън методи към неговия код, но се е опитал да маршалира обекти, създадени от библиотека, използвана в неговите проекти ^^. между другото той също е много заинтересован от разбирането на singleton_class поведение вътре в ruby ​​също. - person criess; 21.05.2014
comment
Крис защо се обръщаш към питащия в трето лице??? Да не си ти питащия??? Объркан съм!!! :Д - person mdesantis; 21.05.2014
comment
всъщност съм. Така че наистина можете да спрете да се обърквате. - person criess; 21.05.2014
comment
За да завършим отговора още малко: marshal_dump може да прави всичко, което поиска, но това, което връща, ще бъде предадено на marshal_load като аргумент. след това marshal_load трябва да разбере как да пресъздаде оригиналния обект (по този начин marshal_dump трябва по някакъв начин да му достави всичко, което е необходимо). В моя случай родителският ми клас беше Hash. Така че използвах JSON.generate(self) като marshal_dump и след това пресъздадох временен хеш с помощта на JSON.parser(arg) и използвах ключовете в temp трябва да пресъздам обекта. - person pedz; 06.11.2015

Едноредовият начин за (обикновено) премахване на информация за сингълтън от instance_of_x:

instance_of_x = instance_of_x.dup
person histocrat    schedule 21.05.2014

Не мисля, че има такова нещо е remove_singleton_class_information!, но можете да опитате програмно да дедефинирате нещата в класа singleton.

Според документацията Marshal не t като обекти с единични методи. С известно експериментиране изглежда, че също не харесва обекти с единичен клас, който има променливи на екземпляр.

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

Забележете колко много се опитах да пречупя Маршал. Дадох на сингълтона променлива на класа, променлива на екземпляр, метод, негов собствен сингълтон метод и константа. Но Marshal работи добре, ако просто премахнете метода singleton и променливата на екземпляра.

Въпреки това не мисля, че това е правилният начин за маршалиране на такъв обект. Отговорът на @mdesantis за дефиниране на вашите собствени marshal_dump и marshal_load е по-правилен.

person Max    schedule 21.05.2014

Измислих този метод, който рекурсивно премахва singleton_methods от обект и от обекти, присвоени на променливи на екземпляр:

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
person Sam Woods    schedule 30.06.2016