Tom Lehman
Tom Lehman

Reputation: 89373

Generate an HTML table from an array of hashes in Ruby

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>

Upvotes: 11

Views: 12465

Answers (6)

sawa
sawa

Reputation: 168269

The dom gem that I have developed has the functionality of what you want to do. You can create a table like this every easily within Ruby code:

require "dom"
[%w[aaa bbb], %w[ccc ddd]].dom(:td, :tr, :table)
# => "<table><tr><td>aaa</td><td>bbb</td></tr><tr><td>ccc</td><td>ddd</td></tr></table>"

Upvotes: 1

Tilo
Tilo

Reputation: 33752

# modified from Harish's answer, to take care of sparse hashes:

require 'builder'

def hasharray_to_html( hashArray )
  # collect all hash keys, even if they don't appear in each hash:
  headers = hashArray.inject([]){|a,x| a |= x.keys ; a}  # use array union to find all unique headers/keys                              

  html = Builder::XmlMarkup.new(:indent => 2)
  html.table {
    html.tr { headers.each{|h| html.th(h)} }
    hashArray.each do |row|
      html.tr { row.values.each { |value| html.td(value) }}
    end
  }
  return html
end

Upvotes: 14

Alston
Alston

Reputation: 1226

Matchu's answer inspired me a lot and I modified it to self-defined methods instead of changing the built-in class (don't do this unless you have a really good reason).

In addition in generating a table, Array's structure maybe much more convenient and intuitive to access elements.

Let the whole table stored in a 2-D array, say

@table_array = [
                 ["Name","Gender","Age"],
                 ["Andy","M","20"],
                 ["Mary","F","19"],
                 ["Tony","M","18"]
              ]

in which each the first element serves as the table header and the rest is table content. Now we can use the well-formatted table_array and a table class attribute to generate a table html code:

def ToCell (tag,value)
    value.map{ |c| "<#{tag}>#{c}</#{tag}>" }.join   
end

def ToTable (table_array, table_class)
    headers = "<tr>" + ToCell('th',table_array[0]) + "</tr>"
    cells = table_array[1..table_array.count].map{ |each_row|
        "<tr>#{ToCell('td',each_row)}</tr>"             
    }.join

    table = "<table class=\"#{table_class}\"><thead>#{headers}</thead><tbody>#{cells}</tbody></table>"
end

and embed it in .erb file

<%= ToTable(@table_array,"table").html_safe %>

the output would be something like this if u see from the browser

<table class="table">
     <thead>
            <tr><th>Name</th><th>Gender</th><th>Age</th></tr>
     </thead>
     <tbody>
            <tr><td>Andy</td><td>M</td><td>20</td></tr>
            <tr><td>Mary</td><td>F</td><td>19</td></tr>
            <tr><td>Tony</td><td>M</td><td>18</td></tr>
     </tbody>
</table>

Upvotes: 1

Harish Shetty
Harish Shetty

Reputation: 64373

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>

Upvotes: 10

Mladen Jablanović
Mladen Jablanović

Reputation: 44110

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/>"

Upvotes: 4

Matchu
Matchu

Reputation: 85862

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 :)

Upvotes: 3

Related Questions