I am currently exporting a CSV file from my rails app and it is working fine but i would like to add a bit more data to the csv.
Currently i'm using:
CSV.generate do |csv|
csv << column_names
all.each do |item|
csv << item.attributes.values_at(*column_names)
end
end
To generate a csv with all of the data from the target model but would like to add an additional column, manufacturer_name which will be taken from a parent model.. Something like:
CSV.generate do |csv|
csv << column_names,
csv << "manufacturer_name"
all.each do |item|
csv << item.attributes.values_at(*column_names),
csv << Manufacturer.find(item.manufacturer_id).first().name
end
end
How would i write this correctly so that the "manufacturer_name" get set to a new column header and the manufacturer name of each item is pulled in and put in the correct column?
CSV.generate do |csv|
names = column_names << 'manufacturer_name'
csv << names
all.each do |item|
row = item.attributes.values_at(*column_names)
row << Manufacturer.find(item.manufacturer_id).first.name
csv << row
end
end
Related
I am running a transaction download script through Ruby. I was wondering if it is possible to label each .csv it creates with the current date/time the script was run. Below is the end of the script.
CSV.open("transaction_report.csv", "w") do |csv|
csv << header_row
search_results.each do |transaction|
transaction_details_row = header_row.map{ |attribute| transaction.send(attribute) }
csv << transaction_details_row
end
end
Like this?
CSV.open("transaction_report-#{Time.now}.csv", "w") do |csv|
csv << header_row
search_results.each do |transaction|
transaction_details_row = header_row.map{ |attribute| transaction.send(attribute) }
csv << transaction_details_row
end
end
This just appends the time of generation to the file name. For example:
"transaction_report-#{Time.now}.csv"
# => "transaction_report-2019-10-10 16:09:07 +0100.csv"
If you want to avoid spaces in the file name, you can sub these out like so:
"transaction_report-#{Time.now.to_s.gsub(/\s/, '-')}.csv"
# => "transaction_report-2019-10-10-16:09:40-+0100.csv"
Is that what you're after? It sounds right based on the question, though happy to update if you're able to correct me :)
I have a block in a rails controller that looks like this:
CSV.generate do |csv|
csv << ["Name", "Other Field"]
csv << [#app.name, #app.other_field]
send_data csv, filename: 'test.csv'
end
But then the contents of the downloaded file are simply this:
#<CSV:0x007fe518414bc8>
What am I doing wrong?
CSV.generate returns the generated CSV string, for example:
str = CSV.generate { |csv| csv << %w[a b c]; csv << [1,2,3] }
puts str
gives you:
a,b,c
1,2,3
as output.
You're passing a CSV instance to send_data and it is trying to convert that CSV instance to a string by, apparently, calling to_s on it.
You want to say something more like this:
csv_string = CSV.generate do |csv|
csv << ["Name", "Other Field"]
csv << [#app.name, #app.other_field]
end
send_data csv_string, filename: 'test.csv'
I'm developing a marketplace app where sellers can list items to sell.
I have a download CSV method which lets sellers download their listings.
My method below downloads all columns. I have two questions:
1) How do I specify columns, let's say I only want name, image, price.
2) In the model, the image field just stores the image name such as item.jpg. But I want to export the URL where the image is stored. I'm using Paperclip to load images and Dropbox to save them. So each image has a dropbox url, which I want to export.
my method in the model:
def self.to_csv(listings)
CSV.generate do |csv|
csv << column_names
listings.each do |listing|
csv << listing.attributes.values_at(*column_names)
end
end
end
1) How do I specify columns, let's say I only want name, image, price.
You need to change the .values_at(*column_names) part to select only the columns you want:
def self.to_csv(listings)
wanted_columns = [:name, :image, :price]
CSV.generate do |csv|
csv << wanted_columns
listings.each do |listing|
csv << listing.attributes.with_indifferent_access.values_at(*wanted_columns)
end
end
end
2) export the image's URL in the CSV file
You probably have a method like image.url which returns the URL of the image:
def self.to_csv(listings)
wanted_columns = [:name, :price]
CSV.generate do |csv|
csv << wanted_columns + [:image]
listings.each do |listing|
attrs = listing.attributes.with_indifferent_access.values_at(*wanted_columns)
attrs.push(listing.image.url) # if image is not always present, use `listing.image.try(:url)`
csv << attrs
end
end
end
I'm building an ecommerce app in Rails. I have a method to export the Order model with shipping details. The below method works as-is but I want to add a column into the export from another model.
My order model has a product_id which joins with the product table that has the product name. I want to add the product name. So something like order.product.name. How would I go about adding that into the below?
Here is my method in my order.rb:
def self.to_csv(orders)
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns)
end
end
end
You can simply add it to your line, like so:
def self.to_csv(orders)
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns.insert(-1, :product_name)
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns).insert(-1, order.product.name)
end
end
end
The insert method inserts the given values before the element with the given index.
Negative indices count backwards from the end of the array, where -1 is the last element.
It returns the resulting array.
To simplify, in this case, this:
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns.insert(-1, :product_name)
end
Should behave exactly the same as:
CSV.generate do |csv|
csv << [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip, :product_name]
end
The same principal applies to the array generated by:
order.attributes.values_at(*wanted_columns)
If this is not working, try the simplified example, and inspect the array for correctness prior to adding it to the array. You may additionally simplify:
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns).insert(-1, order.product.name)
end
to:
csv << orders.first.attributes.values_at(*wanted_columns).insert(-1, orders.first.product.name)
For purposes of troubleshooting...
How do you convert an array of hashes to a .csv file?
I have tried
CSV.open("data.csv", "wb") do |csv|
#data.to_csv
end
but it is blank
Try this:
CSV.open("data.csv", "wb") do |csv|
#data.each do |hash|
csv << hash.values
end
end
If you want the first line of the CSV to contain the keys of the hash (a header row), simply do:
CSV.open("data.csv", "wb") do |csv|
csv << #data.first.keys # adds the attributes name on the first line
#data.each do |hash|
csv << hash.values
end
end
Please read the comment of #cgenco below: He wrote a monkey patch for the Array class.
CSV is smart enough to deal with the non-uniform hashes for you. See the code for CSV::Writer#<<
So, this works, and is a bit simpler than the above examples:
CSV.open("data.csv", "wb", {headers: #data.first.keys} ) do |csv|
#data.each do |hash|
csv << hash
end
end
If the keys are not the same in all rows, the current answers fail. This is the safest approach:
data = [{a: 1, b: 2}, {b: 3, c: 4}]
CSV.open("data.csv", "w") { |csv|
headers = data.flat_map(&:keys).uniq
csv << headers
data.each { |row|
csv << row.values_at(*headers)
}
}
All keys will be present in the CSV, even if they don't appear in the first row:
a
b
c
1
2
3
4
If the hashes aren't uniform then you will end up with data in the wrong columns. You should use values_at instead:
CSV.open("data.csv", "wb") do |csv|
keys = #data.first.keys
csv << keys
#data.each do |hash|
csv << hash.values_at(*keys)
end
end
None of the other answers worked for me for one reason or another, so I'll throw in my contribution as well. Here's what worked for me for my array of hashes with ruby 2.7:
headers = data.map(&:keys).flatten.uniq
CSV.open("data.csv", "wb", {headers: headers} ) do |csv|
csv << headers
data.each do |hash|
csv << hash
end
end