Как программно удалить одноэлементную информацию об экземпляре, чтобы сделать ее маршалированной?

Я создал объект, который не удалось маршалировать из-за «одноэлементного определения метакласса, выполняемого во время выполнения» (верно ли это описание того, что делает код?).

Это выполняется следующим кодом:

# 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 экземпляра любого рубинового класса? Что-то вроде 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
Крисс, почему ты говоришь о спрашивающем в третьем лице??? Не ты ли спрашивающий??? Я смущен!!! :D - 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) и использовал ключи во временных файлах для воссоздания объекта. - 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 не Мне нравятся объекты с одноэлементными методами. После некоторых экспериментов кажется, что ему также не нравятся объекты с одноэлементным классом, который имеет переменные экземпляра.

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