2009-06-26 11 views
0

Je crée un cadre dans lequel les objets doivent être créés selon un fichier XML prédéfini. Par exemple, si dans le fichier XML suivants se produit:Conception pour l'application Ruby en utilisant la programmation méta

<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> 

Dans Ruby, cela devrait vous permettre de créer un objet comme suit:

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

Note: Pour les champs où la « valeur 'est défini dans le fichier xml, aucune valeur ne doit être acceptée dans la méthode initialize, mais plutôt définie par la classe elle-même comme valeur par défaut.

Des implémentations recommandées pour cela? Je suis en train de regarder les screencasts de Dave Thomas à propos de la programmation méta, donc cela semble très approprié, mais des suggestions seraient appréciées!

Répondre

2

Eh bien, vous devrez d'abord analyser le code XML. Vous pourriez utiliser une bibliothèque comme Hpricot ou Nokogiri pour cela. Voici un exemple qui va créer une classe de Man étant donné que le noeud de type de 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 

Ce n'est pas le bit le plus élégant de métaprogrammation que vous verrez jamais, mais je ne peux pas penser à la façon de le rendre plus simple en ce moment. Le premier bit obtient le nom de classe et crée une classe vide par ce nom, le second obtient les attributs du XML et le troisième est la seule métaprogrammation réelle. C'est une définition de classe qui utilise cette information (avec le petit problème supplémentaire de devoir vérifier le nombre d'arguments, puisque nous ne pouvons pas dire à Ruby "X nombre d'arguments est requis").

+0

Si j'ajoute les paramètres un certain ordre, puis-je les extraire dans le même ordre? J'ai essayé avec 'instance_variables' mais les ai git dans l'ordre inverse. La commande est-elle garantie? –

+0

Les variables d'instance ne sont pas une collection ordonnée, elles n'ont donc pas vraiment d'ordre garanti. La commande est réellement différente entre les versions de Ruby. Si vous souhaitez stocker une séquence de noms de variables d'instance, le mieux est de créer un tableau et de le stocker vous-même. – Chuck

0

ne pas entrer dans l'analyse syntaxique xml, mais en supposant que vous avez aussi loin que l'extraction du tableau suivant:

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

le code est ici pour créer la classe:

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 
Questions connexes