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:
- Read the original CSV file.
- Create a temporary CSV file.
- Insert the updated headers into the temporary CSV file.
- Insert the updated records into the temporary CSV file.
- 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)