Home > Software engineering >  Ruby CSV foreach write to csv using Row object
Ruby CSV foreach write to csv using Row object

Time:09-16

I want to loop over a csv file using CSV.foreach, read the data, perform some operation with it, and write the result to the last column of that row, using the Row object.

So let's say I have a csv with data I need to save to a database using Rails ActiveRecord, I validate the record, if it is valid, I write true in the last column, if not I write the errors.

Example csv:

id,title
1,some title
2,another title
3,yet another title
CSV.foreach(path, "r ", headers: true) do |row|
  archive = Archive.new(
    title: row["title"]
  )
  archive.save!
  row["valid"] = true
  
rescue ActiveRecord::RecordInvalid => e
  row["valid"] = archive.errors.full_messages.join(";")
end

When I run the code it reads the data, but it does not write anything to the csv. Is this possible?

Is it possible to write in the same csv file?

Using:

  • Ruby 3.0.4

CodePudding user response:

The row variable in your iterator exists only in memory. You need to write the information back to the file like this:

new_csv = ["id,title,valid\n"]

CSV.foreach(path, 'r ', headers: true) do |row|
  row["valid"] = 'foo'
  new_csv << row.to_s
end

File.open(path, 'w ') do |f|
  f.write new_csv
end

CodePudding user response:

Maybe this is over-engineering things a bit. But I would do the following:

  1. Read the original CSV file.
  2. Create a temporary CSV file.
  3. Insert the updated headers into the temporary CSV file.
  4. Insert the updated records into the temporary CSV file.
  5. Replace the original CSV file with the temporary CSV file.
csv_path = 'archives.csv'
input_csv = CSV.read(csv_path, headers: true)
input_headers = input_csv.headers

# using an UUID to prevent file conflicts
tmp_csv_path = "#{csv_path}.#{SecureRandom.uuid}.tmp"
output_headers = input_headers   %w[errors]

CSV.open(tmp_csv_path, 'w', write_headers: true, headers: output_headers) do |output_csv|
  input_csv.each do |archive_data|
    values  = archive_data.values_at(*input_headers)
    archive = Archive.new(archive_data.to_h)

    archive.valid?
    # error_messages is an empty string if there are no errors
    error_messages = archive.errors.full_messages.join(';')

    output_csv << values   [error_messages]
  end
end

FileUtils.move(tmp_csv_path, csv_path)
  • Related