我正在制作一个框架,其中应根据预定义的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关于元编程的截屏视频, 所以这看起来非常合适,但任何建议都会受到赞赏!
答案 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