Rails XML Builder - Code refactoring
-
11-09-2019 - |
Question
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
Solution
I had the same issue with using the send method and getting tags that looked like <send:id>12</send:id>
. To resolve, I used the "tag!" method. So I think your 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
OTHER TIPS
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.
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).