46

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?

tbuehlmann
  • 9,032
  • 5
  • 27
  • 33
legendary_rob
  • 12,792
  • 11
  • 56
  • 102
  • 3
    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. – Paul Hoffer Nov 18 '11 at 20:37

8 Answers8

69

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)

Joe Goggins
  • 1,328
  • 12
  • 10
44

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
Sławosz
  • 11,187
  • 15
  • 73
  • 106
  • 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! :) – legendary_rob Nov 18 '11 at 14:21
33

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
Tom Rossi
  • 11,604
  • 5
  • 65
  • 96
28

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.

rudolph9
  • 8,021
  • 9
  • 50
  • 80
4

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
peter
  • 41,770
  • 5
  • 64
  • 108
3

[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

pregenRobot
  • 147
  • 1
  • 13
1

Try this:

require 'csv'
data = { 'one' => '1', 'two' => '2', 'three' => '3' }

CSV.open("data.csv", "a+") do |csv|
        csv << data.keys
        csv << data.values
end
Santosh Sharma
  • 2,114
  • 1
  • 17
  • 28
1

Lets we have a hash,

hash_1 = {1=>{:rev=>400, :d_odr=>3}, 2=>{:rev=>4003, :d_price=>300}}

The above hash_1 having keys as some id 1,2,.. and values to those are again hash with some keys as (:rev, :d_odr, :d_price). Suppose we want a CSV file with headers,

headers = ['Designer_id','Revenue','Discount_price','Impression','Designer ODR']

Then make a new array for each value of hash_1 and insert it in CSV file,

CSV.open("design_performance_data_temp.csv", "w") do |csv|
 csv << headers
 csv_data = []
 result.each do |design_data|
  csv_data << design_data.first
  csv_data << design_data.second[:rev] || 0
  csv_data << design_data.second[:d_price] || 0
  csv_data << design_data.second[:imp] || 0
  csv_data << design_data.second[:d_odr] || 0
  csv << csv_data
  csv_data = []
 end
end

Now you are having design_performance_data_temp.csv file saved in your corresponding directory. Above code can further be optimized.

shubham mishra
  • 1,183
  • 14
  • 21