0

I found this code and it fits me nicely, i transformed it into this one:

def write_in_file(file_name, hash)
    column_names = hash.first.keys
    s=CSV.generate do |csv|
        csv << column_names
        hash.each do |x|
            csv << x.values
        end
    end
File.write("#{file_name}.csv", s)
end

This is how my array of hashes looks:

[
  {:Name => "John", :Age => 26, :Country => America},
  {:Name => "Ivan", :Age => 34, :Country => Russia},
  {:Name => "Pablo", :Age => 20, :Country => Columbia}
]

But the problem is that every time I call this method - it rewrites whole file. How to change it, if i want to save this headers and add new information every iteration?

Stefan
  • 109,145
  • 14
  • 143
  • 218
gurman
  • 25
  • 5
  • Open the file in _append_ mode (`mode: 'a'`) and skip writing the header row on subsequent writes. – Stefan Dec 13 '21 at 12:20
  • Write "mode: 'a'" instead of 's'? And how to skip writing the header in subsequent writes? – gurman Dec 13 '21 at 12:45
  • No, `s` is your content. It's `File.write("#{file_name}.csv", s, mode: 'a')`, consult the [docs](https://ruby-doc.org/core-3.0.3/IO.html#write-method) for details. For the headers: you could initially check whether the file exists. If not you write the header row. If it exists, you assume that the headers are there and just write the values. – Stefan Dec 13 '21 at 12:51
  • Thank you for solving my problem! :) – gurman Dec 13 '21 at 13:15
  • @gurman "a" is short for "append". – steenslag Dec 13 '21 at 13:29
  • @gurman To skip the headers you can use: `csv << column_names unless File.exist?("#{file_name}.csv")` – 3limin4t0r Dec 13 '21 at 15:07

1 Answers1

3

You can append to an existing file by using mode: 'a': (see open modes)

File.write("#{file_name}.csv", s, mode: 'a')

To write the headers only on the first run, you could check whether the file exists. In addition, you should use a fixed header and fetch the hash values in that specific order, e.g.:

CSV.generate do |csv|
  csv << %w[Name Age Country] unless File.exist?("#{file_name}.csv")

  hash.each do |x|
    csv << x.values_at(:Name, :Age, :Country)
  end
end

File.write("#{file_name}.csv", s, mode: 'a')

There's also CSV.open which creates the file for you:

CSV.open("#{file_name}.csv", 'a') do |csv|
  csv << %w[Name Age Country] if csv.stat.zero?

  hash.each do |x|
    csv << x.values_at(:Name, :Age, :Country)
  end
end

Since the file will always exist when the block gets executed, the header check needs to be changed: csv.stat returns the file's File::Stat and zero? determines whether the file is empty.

Stefan
  • 109,145
  • 14
  • 143
  • 218