views:

540

answers:

3

I have written the following code in my Rails app to generate XML. I am using Aptana IDE to do Rails development and the IDE shows a warning that the code structure is identical in both the blocks. What changes can be done to the code to remove the duplicity in structure? Is there any other way to write the same?

xml.roles do
    @rolesList.each do |r|
        xml.role(:id => r["role_id"], :name => r["role_name"])
    end
end

xml.levels do
    @levelsList.each do |lvl|
        xml.level(:id => lvl["level_id"], :name => lvl["level_name"])
    end
end
A: 

Something like this?

def build_xml(node_name, node_list)
  xml.send(node_name.pluralize) do
    node_list.each do |node|
      id_str = node["#{node_name}_id"]
      name_str = node["#{node_name}_name"]
      xml.send(node_name, :id => id_str, :name => name_str)
    end
  end
end

build_xml("role", @roleslist)
build_xml("level", @levelslist)

I am trying to use send instead of eval [which i did not do to well: edited it to correct it --thanks to Kathy Van Stone].

Edit 26/12 because the xml builder will capture the send and use it as a xml branch there are two possible options, use the send method instead, like this

      xml.__send__(node_name, :id => id_str, :name => name_str)

but i am not sure whether it will create a <__send__:roles> instead. You could always fall back to

      eval("xml.#{node_name} :id => '#{id_str}', :name => '#{name_str}'")

which should definitely work (but eval should always be used a last resort).

nathanvda
Send doesn't take a string -- it takes a symbol and an (optional) list of arguments (and is a lot saver than eval because of it)
Kathy Van Stone
Thank you: edited code to reflect this (in my ruby it works with a string too, which is converted to a string on the fly (less typing :))
nathanvda
Sorry for getting back so late.. This does not work for me.. "<send:roles><send:role/></send:roles>" is the output that I'm getting.. Any inputs?
Vijay Dev
Hi Vijay, i changed my answer to reflect your comment. I did not try out the code so i offer two suggestions. Let me know which one works for you?
nathanvda
+1  A: 

I had a similar idea to @nathandva, but using send properly:

def list_to_xml(node_name, list)
  xml.send(node_name.pluralize.to_sym) do 
    list.each do |item|
      xml.send(node_name.to_sym, :id => r["#{node_name}_id"], 
               :name => r["#{node_name}_name"])
    end
  end
end

Since it adds visual complexity, this change may not be the best. The biggest question is: if you are likely to make a change to xml.roles structure, are you likely to change xml.levels as well? If so, definitely remove the duplication. It is also important to name the method something that will make sense to you upon reading it; add that point the complexity will be reduced not increased.

Kathy Van Stone
Yes, this looks nice, and at least it is correct :) I believe the availability in ruby of something like send and eval make it really powerful. I would not know how to refactor the above question in another language in a similarly compact fashion.
nathanvda
@nathanvda I could have done it about as easily in Python, and probably in Smalltalk. In Java it would not be worth the effort (but then the xml module would not be possible either).
Kathy Van Stone
sorry for getting back so late.. This does not work for me.. "<send:roles><send:role/></send:roles>" is the output that I'm getting.. Any inputs?
Vijay Dev
+1  A: 

I had the same issue with using the send method and getting tags that looked like 12. You've probably solved this already, but I used the "tag!" method. So I think you're code would look like:

def build_xml(node_name, node_list)
  xml.tag!(node_name.pluralize) do
    node_list.each do |node|
      id_str = node["#{node_name}_id"]
      name_str = node["#{node_name}_name"]
      xml.tag!(node_name, :id => id_str, :name => name_str)
    end
  end
end
Brian Underwood