使用元编程设计Ruby应用程序

时间:2009-06-26 08:04:48

标签: ruby metaprogramming

我正在制作一个框架,其中应根据预定义的XML文件创建对象。 例如,如果在xml文件中发生以下情况:

<type name="man"> 
    <property name="name" type="string">
    <property name="height" type="int">
    <property name="age" type="int">
    <property name="profession" type="string" value="unemployed">
</type> 

在Ruby中,这应该允许您创建如下对象:

man = Man.new('John', 188, 30)

注意:对于在xml中定义'value'的字段,没有值 应该在初始化方法中接受,但应该是 由类本身设置为默认值。

对此推荐的任何实现? 我正在观看Dave Thomas关于元编程的截屏视频, 所以这看起来非常合适,但任何建议都会受到赞赏!

2 个答案:

答案 0 :(得分:2)

好吧,首先你需要解析XML。您可以使用像Hpricot或Nokogiri这样的库。这是一个示例,它将根据Nokogiri的类型节点创建一个Man类:

def define_class_from_xml(node, in_module = Object)
  class_name = node['name'].dup
  class_name[0] = class_name[0].upcase
  new_class = in_module.const_set(class_name, Class.new)

  attributes = node.search('property').map {|child| child['name']}
  attribute_values = node.search('property[@value]').inject({}) do |hash, child|
    hash[child['name']] = child['value']
    hash
  end

  new_class.class_eval do
    attr_accessor *attributes
    define_method(:initialize) do |*args|
      needed_args_count = attributes.size - attribute_values.size
      if args.size < needed_args_count
        raise ArgumentError, "#{args.size} arguments given; #{needed_args_count} needed"
      end
      attributes.zip(args).each {|attr, val| send "#{attr}=", val}
      if args.size < attributes.size
        attributes[args.size..-1].each {|attr| send "#{attr}=", attribute_values[attr]}
      end
    end
  end
end

这不是你所见过的最优雅的元编程,但我想不出如何让它变得更简单。第一个位获取类名,并使用该名称创建一个空类,第二个位从XML获取属性,第三个位是唯一真正的元编程。这是一个使用该信息的类定义(由于我们无法告诉Ruby,因此需要检查参数计数的次要添加麻烦“需要X个参数”)。

答案 1 :(得分:0)

没有进入xml解析,但假设你已经提取了以下数组:

name = 'Man'
props = [["name", "string"], 
         ["height", "int"],
         ["age", "int"],
         ["profession", "string", "unemployed"]]

这是创建类的代码:

def new_class(class_name, attrs)
  klass = Class.new do
    attrs.each do |attr, type, val|
      if val
        attr_reader attr
      else
        attr_accessor attr
      end
    end
  end

  init = ""
  attrs.each do |attr, type, val|
    if val
      if type == "string"
        init << "@#{attr} = '#{val}'\n"
      else # assuming all other types are numeric
        init << "@#{attr} = #{val}\n"
      end
    else
      init << "@#{attr} = #{attr}\n"
    end
  end

  args = attrs.select {|attr, type, val| val.nil?}.map {|attr, type, val| attr}.join(",")

  klass.class_eval %{
    def initialize(#{args})
      #{init}
    end
  }

  Object.const_set class_name, klass
end

name = 'Man'
props = [["name", "string"], ["height", "int"], ["age", "int"], ["profession", "string", "unemployed"]]
new_class(name, props)

man = Man.new('John', 188, 30)

p man