2009-06-26 9 views
0

私は、オブジェクトを定義済みのXMLファイルに従って作成する必要があるフレームワークを作成しています。 Rubyでメタプログラミングを使用した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> 

を、これは次のようにオブジェクトを作成できるようにする必要があります:たとえば、XMLに次のファイル場合は発生し

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

注:フィールドの場合「の値'がxmlに定義されている場合、初期化メソッドで値 を受け入れる必要はありませんが、デフォルト値としてクラス自体によって に設定する必要があります。

この場合、推奨される実装はありますか? 私は現在、Dave Thomasのメタプログラミングに関するスクリーンキャスト、 を見ていますので、これは非常に適していますが、どんな提案も感謝します!

答えて

2

まず、XMLを解析する必要があります。あなたはそれのためにHpricotや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 

それはあなたが表示されますメタプログラミングの最もエレガントなビットではありませんが、私はそれがどの簡素にする方法を考えることはできません現時点では。最初のビットはクラス名を取得し、その名前で空のクラスを作成し、2番目のビットはXMLから属性を取得し、3番目のビットは唯一の実際のメタプログラミングです。この情報を使ったクラス定義です(引数の数を確認する必要があるという面倒な手間がかかります)。なぜなら、Rubyの "引数の数が必要です"ということが分かりませんからです。

+0

私はパラメータを追加する場合特定の順序、私は同じ順序でそれらを抽出することはできますか?私は 'instance_variables'でしようとしましたが、逆の順序でそれらをgitします。注文は保証されていますか? –

+0

インスタンス変数は順序付けされたコレクションではないため、実際に保証された順序はありません。 Rubyのバージョン間で実際には順序が異なります。インスタンス変数名のシーケンスを保存する場合は、配列を作成して自分自身で保存するのが最善の方法です。 – Chuck

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 
関連する問題