views:

87

answers:

3

What's the best way (ideally a gem, but a code snippet if necessary) to generate an HTML table from an array of hashes?

For example, this array of hashes:

[{"col1"=>"v1", "col2"=>"v2"}, {"col1"=>"v3", "col2"=>"v4"}]

Should produce this table:

<table>
  <tr><th>col1</th><th>col2</th></tr>
  <tr><td>v1</td><td>v2</td></tr>
  <tr><td>v3</td><td>v4</td></tr>
</table>
+3  A: 

This doesn't seem particularly difficult to do by hand. Depending on where you're going to use it, this should probably go in its own method somewhere, but here's the little script I just wrote up:

table.rb:

class Array 
  def to_cells(tag)
    self.map { |c| "<#{tag}>#{c}</#{tag}>" }.join
  end
end

rows = [{"col1"=>"v1", "col2"=>"v2"}, {"col1"=>"v3", "col2"=>"v4"}]
headers = "<tr>#{rows[0].keys.to_cells('th')}</tr>"
cells = rows.map do |row|
  "<tr>#{row.values.to_cells('td')}</tr>"
end.join("\n  ")
table = "<table>
  #{headers}
  #{cells}
</table>"
puts table

Output:

<table>
  <tr><th>col1</th><th>col2</th></tr>
  <tr><td>v1</td><td>v2</td></tr>
  <tr><td>v3</td><td>v4</td></tr>
</table>

Obviously, there are some issues - for one, it assumes that row one's headings are the same as all the other headings. You could pre-process and work around this pretty easily, though, by filling in nils in all rows for all headings not properly assigned.

The reason there's not a gem is that generating a table isn't really a huge undertaking. It's amazing what you can do when you buckle down and actually code something yourself :)

Matchu
I just noticed that, since I ended up changing the script, the output is most definitely not that. Edit in just a sec.
Matchu
Edited. Much better.
Matchu
+1  A: 

You can use builder:

require 'builder'

a = [{"col1"=>"v1", "col2"=>"v2"}, {"col1"=>"v3", "col2"=>"v4"}]
builder = Builder::XmlMarkup.new
columns = a.first.keys
builder.table do |t|
  t.tr do |tr|
    columns.each do |col|
      tr.th(col)
    end
  end
  a.each do |row|
    t.tr do |tr|
      columns.each do |col|
        tr.td(row[col])
      end
    end
  end
end
p builder.target
#=> "<table><tr><th>col1</th><th>col2</th></tr><tr><td>v1</td><td>v2</td></tr><tr><td>v3</td><td>v4</td></tr></table><target/>"
Mladen Jablanović
That's us posting the same solution at the same time :-)
KandadaBoggu
+2  A: 

Use the XMLBuilder for this:

data = [{"col1"=>"v1", "col2"=>"v2"}, {"col1"=>"v3", "col2"=>"v4"}]
xm = Builder::XmlMarkup.new(:indent => 2)
xm.table {
  xm.tr { data[0].keys.each { |key| xm.th(key)}}
  data.each { |row| xm.tr { row.values.each { |value| xm.td(value)}}}
}
puts "#{xm}"

Output

<table>
  <tr>
    <th>col1</th>
    <th>col2</th>
  </tr>
  <tr>
    <td>v1</td>
    <td>v2</td>
  </tr>
  <tr>
    <td>v3</td>
    <td>v4</td>
  </tr>
</table>
KandadaBoggu