How to save a hash into a CSV

48,973

Solution 1

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

Solution 2

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)

Solution 3

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

Solution 4

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.

Solution 5

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
Share:
48,973
TheLegend
Author by

TheLegend

#SOreadytohelp. I am a Software Developer here in Cape Town, South Africa. I mostly work on web apps, mostly in Ruby and Rails. I started back in Nov 2011. I work for a company called Peach Payments as their head of engineering. Above all else, I love simple easy to read code. I am a student of the software development process. I find the glue that holds domains together as interesting as the domains themselves.

Updated on July 09, 2022

Comments

  • TheLegend
    TheLegend almost 2 years

    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?

    • Paul Hoffer
      Paul Hoffer over 12 years
      Just so you know, Ruby 1.9 replaced the old CSV module with FasterCSV, so you are actually using FasterCSV. Because it is part of the Standard Library, it is called CSV instead of FasterCSV.
  • TheLegend
    TheLegend over 12 years
    yeah that was the idea. convert it back to an array.. very cool thanks ! and using a block to do it plus 5 points!! high five! :)
  • Nuno Costa
    Nuno Costa almost 9 years
    None of the other answers worked for me in order to save the column headers as well. This answer works just fine
  • Ben Hull
    Ben Hull over 7 years
    CSV.generate is very handy if you don't want to actually output to a file on disk.
  • s2t2
    s2t2 over 5 years
    how does generate compare in performance vs file writing, as the number of rows increases?
  • go2null
    go2null over 5 years
    in ruby v2.5.3, at least, write_headers: true is required in `CSV.open