Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save a hash into a CSV

I am new in ruby so please forgive the noobishness.

I have a CSV with two columns. One for animal name and one for animal type. I have a hash with all the keys being animal names and the values being animal type. I would like to write the hash to the CSV without using fasterCSV. I have thought of several ideas what would be easiest.. here is the basic layout.

require "csv"

def write_file
  h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }

  CSV.open("data.csv", "wb") do |csv|
    csv << [???????????]
  end
end

When I opened the file to read from it I opened it File.open("blabla.csv", headers: true) Would it be possible to write back to the file the same way?

like image 776
TheLegend Avatar asked Nov 18 '11 14:11

TheLegend


6 Answers

If you want column headers and you have multiple hashes:

require 'csv'
hashes = [{'a' => 'aaaa', 'b' => 'bbbb'}]
column_names = hashes.first.keys
s=CSV.generate do |csv|
  csv << column_names
  hashes.each do |x|
    csv << x.values
  end
end
File.write('the_file.csv', s)

(tested on Ruby 1.9.3-p429)

like image 61
Joe Goggins Avatar answered Nov 20 '22 00:11

Joe Goggins


Try this:

require 'csv'
h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
CSV.open("data.csv", "wb") {|csv| h.to_a.each {|elem| csv << elem} }

Will result:

1.9.2-p290:~$ cat data.csv 
dog,canine
cat,feline
donkey,asinine
like image 44
Sławosz Avatar answered Nov 20 '22 00:11

Sławosz


I think the simplest solution to your original question:

def write_file
  h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }

  CSV.open("data.csv", "w", headers: h.keys) do |csv|
    csv << h.values
  end
end

With multiple hashes that all share the same keys:

def write_file
  hashes = [ { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' },
             { 'dog' => 'rover', 'cat' => 'kitty', 'donkey' => 'ass' } ]

  CSV.open("data.csv", "w", headers: hashes.first.keys) do |csv|
    hashes.each do |h|
      csv << h.values
    end
  end
end
like image 33
Tom Rossi Avatar answered Nov 20 '22 00:11

Tom Rossi


CSV can take a hash in any order, exclude elements, and omit a params not in the HEADERS

require "csv"
HEADERS = [
  'dog',
  'cat',
  'donkey'
]

def write_file

  CSV.open("data.csv", "wb", :headers => HEADERS, :write_headers => true) do |csv|
    csv << { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
    csv << { 'dog' => 'canine'}
    csv << { 'cat' => 'feline', 'dog' => 'canine', 'donkey' => 'asinine' }
    csv << { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine', 'header not provided in the options to #open' => 'not included in output' }
  end
end

write_file # => 
# dog,cat,donkey
# canine,feline,asinine
# canine,,
# canine,feline,asinine
# canine,feline,asinine

This makes working with the CSV class more flexible and readable.

like image 25
rudolph9 Avatar answered Nov 19 '22 23:11

rudolph9


I tried the solutions here but got an incorrect result (values in wrong columns) since my source is a LDIF file that not always has all the values for a key. I ended up using the following.

First, when building up the hash I remember the keys in a separate array which I extend with the keys that are not allready there.

# building up the array of hashes
File.read(ARGV[0]).each_line do |lijn|
    case
    when lijn[0..2] == "dn:" # new record
        record = {}
    when lijn.chomp == '' # end record
        if record['telephonenumber'] # valid record ?
            hashes << record
            keys = keys.concat(record.keys).uniq
        end
    when ...
    end
end

The important line here is keys = keys.concat(record.keys).uniq which extends the array of keys when new keys (headers) are found.

Now the most important: converting our hashes to a CSV

CSV.open("export.csv", "w", {headers: keys, col_sep: ";"}) do |row|
  row << keys # add the headers
  hashes.each do |hash|
    row << hash # the whole hash, not just the array of values
  end
end
like image 4
peter Avatar answered Nov 20 '22 00:11

peter


[BEWARE] All the answers in this thread are assuming that the order of the keys defined in the hash will be constant amongst all rows.

To prevent problems (that I am facing right now) where some values are assigned to the wrong keys in the csv (Ex:)

hahes = [
    {:cola => "hello", :colb => "bye"},
    {:colb => "bye", :cola => "hello"}
]

producing the following table using the code from the majority (including best answer) of the answers on this thread:

cola  | colb
-------------
hello | bye
-------------
bye   | hello

You should do this instead:

require "csv"

csv_rows = [
    {:cola => "hello", :colb => "bye"},
    {:colb => "bye", :cola => "hello"}
]

column_names = csv_rows.first.keys

s=CSV.generate do |csv|
  csv << column_names
  csv_rows.each do |row|
    csv << column_names.map{|column_name| row[column_name]} #To be explicit
  end
end

like image 3
pregenRobot Avatar answered Nov 20 '22 01:11

pregenRobot