封送和未定义的属性/类

时间:2012-07-30 19:18:36

标签: ruby

我正在使用ruby marshaling在两个客户端之间发送数据。每个客户端都有一组类定义,它们将用于帮助加载封送数据。这些定义存储在外部ruby文件中,可以随时加载(但通常在启动时)

一个简单的用例是

  • 客户端A marshal转储数据并将其发送给客户端B
  • 客户端B编组加载数据,然后将其写入文件

但是,有时一个客户端发送的数据包含未在其他客户端定义中定义的对象,在这种情况下,另一个客户端应相应地更新其定义。

它可能是一个新的实例变量,应该添加到类xyz的定义中,或者它可能是一个完全新的类。

Marshal#Load遇到未定义的变量时,它会抛出异常(例如:undefined class / method abc)。

我是否有办法接受此异常并相应地更新定义,以便客户端可以愉快地读取数据并将其写出来?

所有类都将包含Marshal已经知道如何编码/解码的数据,例如字符串,数组,哈希,数字等。没有任何数据需要自定义dump / {{1}方法。

1 个答案:

答案 0 :(得分:1)

我的解决方案是自动创建类(和常量层次结构,即Foo::Bar::Baz)并使类自动响应以属性访问尝试。

class AutoObject
  def method_missing(*args,&b)
    if args.size == 1
      name = args[0]
      if instance_variable_defined? "@#{name}"
        self.class.send :attr_accessor, name
        send(*args)
      else
        super
      end
    elsif args.size == 2 && args[0].to_s[/=$/]
      name = args[0].to_s[0...-1]
      if instance_variable_defined? "@#{name}"
        self.class.send :attr_accessor, name
        send(*args)
      else
        super
      end
    end
  end
end

def Marshal.auto_load(data)
  Marshal.load(data)
rescue ArgumentError => e
  classname = e.message[%r(^undefined class/module (.+)$), 1]
  raise e unless classname

  classname.split("::").inject(Object) do |outer, inner|
    if !outer.const_defined? inner
      outer.const_set inner, Class.new(AutoObject)
    else
      outer.const_get inner
    end
  end
  retry
end

这可以很容易地扩展到记录所有创建的类,甚至可以确定它们可能具有的实例变量。然后,这可以帮助您更新文件,也许以编程方式。