I want to export some ActiveRecords in CSV format. After check some tutorials, I found this:
def export_as_csv(equipments)
attributes = %w[id title description category_id]
CSV.generate(headers: true) do |csv|
csv << attributes
equipments.each do |equipment|
csv << equipment.attributes.values_at(*attributes)
end
return csv
end
end
The problem is, I want to manipulate all in memory in my tests(i.e. I don't want to save the file in the disk). So, when I receive this csv object as return value, how I can iterate through rows and columns? I came from Python and so I tried:
csv = exporter.export_as_csv(equipments)
for row in csv:
foo(row)
But obviously didn't work. Also, the equipments are surely not nil.
CSV.generate returns string formatted according csv rules.
So the most obvious way is to parse it and iterate, like:
csv = exporter.expor_as_csv(equipments)
CSV.parse(csv).each do |line|
# line => ['a', 'b', 'c']
end
After some videos, I found that the return was the problem. Returning the CSV I was receiving a CSV object, and not the CSV itself.
Related
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
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| # error here, see edit note below
row["valid"] = 'foo'
new_csv << row.to_s
end
File.open(path, 'w+') do |f|
f.write new_csv
end
[EDIT] the 'r+' option to foreach is not valid, it should be 'r'
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)
I'd like multiple pieces of data on different lines within the same CSV cell like this:
"String" 2-15-2021 05:26pm
"String ..."
"String..."
I have tried the following and ended up with \n in the cell and not an actual new line, like this "2-15-2021 05:26pm \nHi, it's ...".
["\n", time, text.body].join("\n")
[time, text.body, "\n"].join("\n")
[time, text.body].join("\n")
The input data is an array of hashes. The output of a row is a hash with keys and values, one of the values is a list of strings (or this can be a list of lists of string, I am playing with what I can get to work). The list of strings is where I am trying to add line breaks.
I am using this to create the csv:
CSV.open("data.csv", "wb") do |csv|
csv << list.first.keys
list.each do |hash|
csv << hash.values
end
end
I ended up needing a list of strings that I could then join and add new lines onto.
values = []
values.push("#{time}, #{text.body}")
# And then in the hash for the csv, setting the value for that column like this:
{ message: values.join("\n\n")}
I am trying to print my values in CSV file like following where data is array of hashes.
UPDATES:
CSV.open(fn, "wb") do |csv|
#first rows are always headers and the headers value is generated from the array of hashes
data.each do |name, values|
csv << [name, values.join(",")]
end
and values has data like : true,false,false,false and name is an array with data like: light.
But for some reason my columns are only 2 instead of 5. The values column is concatenated in one column.
How can I achieve multiple columns using above code ?
I think this should work:
CSV.open(fn, "wb") do |csv|
data.each do |name, values|
csv << [name, *values]
end
end
http://ruby-doc.org/core-2.0.0/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion
Currently, I'm using Rails and able to export, but there are values within the DB that are in a numeric format, and I need them to be translated into an alphanumeric format. I have the translations, but I don't know how to do it while exporting to CSV
Here's my current snippet of code to export to CSV
def self.to_csv(mycolumns)
CSV.generate() do |csv|
csv << mycolumns
all.each do |ccts|
csv << ccts.attributes.values_at(*mycolumns)
end
end
end
So my initial thought was that I could go into each ccts and edit them, but I don't know how to access the value within the hash and alter it. And it's only for a specific column. For instance, if this table was for fruits, and one of the column names was Name. If I wanted to change a value of 0041 into Apple, but only within the Name column, I'm just not sure how to accomplish this.
The csv export code is very compact, especially this line:
csv << ccts.attributes.values_at(*mycolumns)
That makes it difficult to think about how to change it.
First think how you would export your value if it was a single column. It may look something like:
if column_name == :name
lookup_fruit_name(ccts.name)
else
ccts[column_name]
end
Now you need all the values of a ccts inside an array, so it can be sent to csv:
values = mycolums.map do |column_name|
if column_name == :name
lookup_fruit_name(ccts.name)
else
ccts[column_name]
end
end
csv << values
Then just place this inside the inner loop of your original export method.
If you think more functional, you just write an instance method that gets your value and does a conversion depending on the column:
def csv_value_for(column_name)
if column_name == :name
lookup_fruit_name( self.name )
else
self[column_name]
end
end
Then you can use it like this:
csv << mycolumns.map{|col| ccts.csv_value_for(col) }
I would like to ask if any one know how correctly transfer Mongoid data in to CSV document?
I got a model Record and I want every row from Record to become row in CSV document. I got 90 columns (keys) in the Record and I want to exclude some of the them from CSV document but I do not wont manually type every key which I want to be on CSV document. My code is
#all_rows = Record.all
CSV.open(Rails.root.join('public', 'downloads', "file.csv"), "w") do |csv|
#allrows.all.each do |record|
csv << record
end
But it does not work I am getting error
undefined method `map' for #<Record:0x007f9cd9e242f8>
if i adding record.to_s
i am gating document full of records like this #<Record:0x007f801ba60d68>
If any one can please help me to fix it! Thank you!
You are using << method on csv (documentation), which expects to be called with array as an argument. That is why it tries to perform map method on your record.
Solution for your problem is adding array of attributes instead of record object. There is method attributes that will return hash with all attributes.
ignored_attributes = ["attribute_you_dont_want", "another_attribute"]
#all_rows = Record.all
CSV.open(Rails.root.join('public', 'downloads', "file.csv"), "w") do |csv|
#all_rows.each do |record|
csv << record.attributes.delete_if{ |attr, value| ignored_attributes.include?(attr) }.values
end
end
Note that I wrote #all_rows.each, you shouldn't call all method again.
This code will perform delete_if method on attributes hash and will remove any attributes with names included in ignored_attributes array. delete_if returns hash on which you can call values method to return only array of values.