I am trying to write a Rails helper method to convert a nested hash into a nested HTML list.
For example:
{
:parent => "foo",
:children => [
{
:parent => "bar",
:children => [
{
:parent => "baz",
:children => []
}
]
}
]
}
should become:
<ul>
<li>foo</li>
<ul>
<li>bar</li>
<ul>
<li>baz</li>
</ul>
</ul>
</ul>
The hash may have any number of levels, and any number of parents per level.
What is the best way to achieve this please?
You can make a recursive method to render to hash to a nested set of lists. Place this in your relevant helper:
def hash_list_tag(hash)
html = content_tag(:ul) {
ul_contents = ""
ul_contents << content_tag(:li, hash[:parent])
hash[:children].each do |child|
ul_contents << hash_list_tag(child)
end
ul_contents.html_safe
}.html_safe
end
Zach Kemp's answer very effectively addresses the question. If you are looking for something a bit more generic (a nested hash for which you will not know the key names), as I was, the following module may be helpful (also at https://github.com/sjohnson/auto_hash_display with more details):
module HashFormatHelper
# These methods add classes to the HTML structure that are defined in Bootstrap (and can be defined for other CSS frameworks)
def format_hash(hash, html = '')
hash.each do |key, value|
next if value.blank?
if value.is_a?(String) || value.is_a?(Numeric)
html += content_tag(:ul, class: 'list-group') {
ul_contents = ''
ul_contents << content_tag(:li, content_tag(:h3, key.to_s.underscore.humanize.titleize), class: 'list-group-item')
ul_contents << content_tag(:li, value, class: 'list-group-item')
ul_contents.html_safe
}
elsif value.is_a?(Hash)
html += content_tag(:ul, class: 'list-group') {
ul_contents = ''
ul_contents << content_tag(:li, content_tag(:h3, key.to_s.underscore.humanize.titleize), class: 'list-group-item')
inner = content_tag(:li, format_hash(value), class: 'list-group-item')
ul_contents << inner
ul_contents.html_safe
}
elsif value.is_a?(Array)
html += format_array(value)
else
Rails.logger.info "Unexpected value in format_hash: #{value.inspect}"
Rails.logger.info "value type: #{value.class.name}"
end
end
html.html_safe
end
def format_array(array, html = '')
array.each do |value|
if value.is_a?(String)
html += content_tag(:div, value).html_safe
elsif value.is_a?(Hash)
html += format_hash(value)
elsif value.is_a?(Array)
html += format_array(value)
else
Rails.logger.info "Unexpected value in format_array: #{value.inspect}"
Rails.logger.info "value type: #{value.class.name}"
end
end
html
end
end
This code can also be used to display XML by setting the hash value equal to Hash.from_xml(your_xml_data) and then passing that in to format_hash(hash).
Please note that the from_xml method may strip off XML tag attributes, so it works best for XML that doesn't have attributes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With