Дизайн для приложения Ruby с использованием метапрограммирования - PullRequest
0 голосов
/ 26 июня 2009

Я делаю каркас, в котором объекты должны создаваться в соответствии с предопределенным 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, значение отсутствует должно быть принято в методе инициализации, но должно быть устанавливается самим классом в качестве значения по умолчанию.

Любые рекомендуемые реализации для этого? Сейчас я смотрю скриншоты Дэйва Томаса о метапрограммировании, так что это выглядит очень подходящим, но любые предложения будут оценены!

Ответы [ 2 ]

2 голосов
/ 26 июня 2009

Ну, для начала вам нужно разобрать XML. Для этого вы можете использовать такую ​​библиотеку, как Hpricot или Nokogiri. Вот пример, который создаст класс Man для данного узла типа из 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 голосов
/ 26 июня 2009

Не входя в синтаксический анализ 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
...