Rails: send_data(csv) putting object id in csv file rather than csv info - ruby-on-rails

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'

Related

How to check header exist before import data in Ruby CSV?

I want to write header only 1 time in first row when import data to csv in ruby, but the header is written many time on output file.
job_datas.each do |job_data|
#company_job = job data coverted etc....
save_job_to_csv(#company_job)
end
def save_job_to_csv(job_data)
filepath = "tmp/jobs/jobs.csv"
CSV.open(filepath, "a", :headers => true) do |csv|
if csv.blank?
csv << CompanyJob.attribute_names
end
csv << job_data.attributes.values
end
end
Any one can give me solution? Thank you so much!
You are calling save_job_to_csv the method for each job_data and pushing header every time csv << CompanyJob.attribute_names
filepath = "tmp/jobs/jobs.csv"
CSV.open(filepath, "a", :headers => true) do |csv|
# push header once
csv << CompanyJob.attribute_names
# push every job record
job_datas.each do |job_data|
#company_job = job data coverted etc....
csv << #company_job.attributes.values
end
end
The above script can be created wrapped a method but if you like to write a separate method that just saves the CSV, then you need to refactor the script when you first prepare an array of values holding header and pass it to a method that just saves to CSV.
You could do something similar to this:
def save_job_to_csv(job_data)
filepath = "tmp/jobs/jobs.csv"
unless File.file?(filepath)
File.open(filepath, 'w') do |file|
file.puts(job_data.attribute_names.join(','))
end
end
CSV.open(filepath, "a", :headers => true) do |csv|
csv << job_data.attributes.values
end
end
It just checks beforehand if the file exists and if not it adds the header. If you want tabs as column separators, you just have to change the value for the join function and add the col_sep parameter to CSV.open():
file.puts(job_data.attribute_names.join("\t"))
CSV.open(filepath, "a", :headers => true, col_sep: "\t") do |csv|

how to add custom column to CSV during generation in rails

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

Rails overwrite CSV

I'm currently using this code:
CSV.open "application.csv", "a+" do |csv|
csv << [ "#{params[:first_name]}", "#{params[:last_name]}","#{params[:company]}","#{params[:email]}", "#{params[:phone]}", "#{params[:business]}", "#{params[:services]}", "#{params[:employees]}", "#{params[:turnover]}" ]
end
Which writes an extra row to the csv each time, what can I put instead of "a+" that will overwrite the entire file each time, so it always only has one row?
You should use 'w' mode. BTW, why do you write "#{params[:first_name]}" where params[:first_name] would be enough? The code should look:
CSV.open 'application.csv', 'w' do |csv|
csv << [params[:first_name], params[:last_name], params[:company], params[:email], params[:phone], params[:business], params[:services], params[:employees], params[:turnover], params[:c4l_services]]
end

convert array of hashes to csv file

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

ArgumentError on CSV output

i'm getting the following error when trying to generate a CSV:
ArgumentError in ProductsController#schedulecsv
wrong number of arguments (0 for 1)
My Products controller is set up as follows:
def schedulecsv
products = Product.find(:all)
filename ="schedule_#{Date.today.strftime('%d%b%y')}"
csv_data = CSV.generate do |csv|
csv << Product.csv_header
products.each do |p|
csv << p.to_csv
end
end
send_data csv_data,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename=#{filename}.csv"
end
Does anyone have any pointers here? Driving me bonkers!
Thanks!
From source of csv.rb place in /usr/lib/ruby/(version of your ruby gem)/csv.rb (on my machine)
Here is source code of CSV class's generate method
def CSV.generate(path, fs = nil, rs = nil, &block)
open_writer(path, 'w', fs, rs, &block)
end
generate method require filename as parameter.it will make file with given name,but You are calling CSV.generate filename was missed
so you have to passed name of file in generate call!
filename ="schedule_#{Date.today.strftime('%d%b%y')}"
CSV.generate filename do |csv|
csv << Product.csv_header
products.each do |p|
csv << p.to_csv
end
end

Resources