2009-06-26 18 views
0

我正在製作一個框架,根據預定義的XML文件創建對象。 例如,如果在XML文件中會出現以下情況:使用元編程設計Ruby應用程序

<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中定義的,在初始化方法中不應該接受 值,而應該是由類本身設置的 作爲默認值。

任何推薦的實現? 我目前在觀看Dave Thomas關於元編程的屏幕錄像, 所以這看起來很合適,但任何建議,將不勝感激!

回答

2

那麼,首先你需要解析XML。你可以使用像Hpricot或Nokogiri這樣的庫。下面是將給出從引入nokogiri該類型節點創建一個男人類的例子:

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個參數」),所以需要檢查參數計數的次要麻煩。

+0

如果我添加的參數一定的順序,我可以按照相同的順序提取它們嗎?我嘗試了'instance_variables',但是按照相反的順序對它們進行了git。訂單是否有保證? – 2009-06-28 19:55:30

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