This may have been asked long time ago but I couldn't find a good answer anywhere.
Current situation of my application is:
Rails API (no views)
TypeScript JS
Now, i have been given a task to build a CSV feature...
Question is,
how do i do it in rails api?
how should my frontend handle this?
this is my not working endpoint:
def download_csv
headers = ['column one', 'column two', 'column three']
csv_data = CSV.generate(headers: true) do |csv|
csv << headers
csv << ["A", "B", "C"]
end
csv_data.strip
send_file(
csv_data,
disposition: "attachment; csv-.csv",
)
end
You want to use send_data and not send_file:
def download_csv
# Move the CSV generation out of the the controller
# for example into a service object where it can be tested in isolation
headers = ['column one', 'column two', 'column three']
csv_data = CSV.generate(headers: true) do |csv|
csv << headers
csv << ["A", "B", "C"]
end
send_data(
csv_data,
filename: 'myfile.csv', # suggests a filename for the browser to use.
type: :csv, # specifies a "text/csv" HTTP content type
)
end
To use send_file you would actually need to write the CSV to a file on the servers file system - which might make sense if the CSV generation is expensive.
Related
In ActionMailer, I am trying to convert an array of array to a CSV and make sure the file can be read as if it were converted in UTF8 with BOM.
Previously, I was copying the content to a new file in Sublime Text, and clicking File > Save With Encoding > UTF8 with BOM otherwise the characters would end up messed up.
How can I achieve the same encoding while sending an in-memory CSV through ActionMailer (I am never writing the file to my disk)
Here is my sample code for sending the email
class CSVMailer < ApplicationMailer
def csv(csv_as_array_of_array,
to:,
cc: [],
from: 'messages-noreply#example.com',
reply_to: 'me#example.com',
subject: 'Here is your CSV made with love 😘',
filename: 'your_csv_made_with_love.csv'
)
attach_csv(csv_as_array_of_array, filename: filename)
mail(
to: to,
cc: cc,
subject: subject
)
end
private
def attach_csv(array_of_arrays, filename:)
attachments[filename] = {
mine_type: 'text/csv',
content: CSV.generate(col_sep: ';') do |csv|
array_of_arrays.each do |row|
csv << row
end
end
}
end
end
Alright here is how I did it, assuming you are sending the following array of arrays to the mailer function
csv_as_array_of_array = [
['header1', 'header2'],
['row1cell1', 'row1cell2'],
...
]
a simple utility function that prepends the UTF8 bom to the csv is enough
# Small utility method
def Utility.with_utf8_bom(content)
"\uFEFF" + content
end
So in the ActionMailer class
class CSVMailer < ApplicationMailer
# app/mailers/csv_mailer.rb
def send_email_with_csv(csv_as_array_of_array)
...
attach_csv(csv_as_array_of_array, filename: filename)
mail(to: ...)
end
private
def attach_csv(array_of_arrays, filename:)
attachments[filename] = {
mine_type: 'text/csv',
content: Utility.with_utf8_bom(
CSV.generate(col_sep: ';', encoding: Encoding::UTF_8) do |csv|
array_of_arrays.each do |row|
csv << row
end
end
)
}
end
This should have been such an easy thing... buy I can't for the life of me figure out how to parse a CSV file that doesn't seem to have a specific encoding.
File.open(Rails.root.join('data', 'mike/test-csv.csv'), 'rb') { |f| f.read }
=> "ID,\x00Q\x00u\x00a\x00n\x00t\x00i\x00t\x00y\n\x006\x00e\x005\x004\x009\x001\x00e\x007\x00-\x007\x00f\x001\x005\x00-\x004\x001\x007\x00d\x00-\x00a\x004\x000\x003\x00-345\x00,\x00\x005\x000\x00.\x000\x000\x000\x000\x000\x000\x000\x000\x00\n"
Here's a gist of it, can't figure out a way to post the specific CSV.
All I get from checking the encoding of the file is that it's in binary format, any thoughts on how I could get it into a normal csv?
Note: This is a downloaded CSV so converting it to another encoding via opening it in excel and exporting (or something like that) is not an option :)
Thanks!
Updating with attempted solution 1:
path = Rails.root.join('data', 'mike/test-csv.csv')
CSV.read(path, {:headers => true, :encoding => 'utf-8'}).each do |d|
puts d
end
Result: 6e5491e7-7f15-417d-a403-345,50.00000000
While this is correct, it ONLY works with puts, for example:
CSV.read(path, {:headers => true, :encoding => 'utf-8'}).map { |row| row }
=> [#<CSV::Row "ID":"\u00006\u0000e\u00005\u00004\u00009\u00001\u0000e\u00007\u0000-\u00007\u0000f\u00001\u00005\u0000-\u00004\u00001\u00007\u0000d\u0000-\u0000a\u00004\u00000\u00003\u0000-345\u0000" "\u0000Q\u0000u\u0000a\u0000n\u0000t\u0000i\u0000t\u0000y":"\u0000\u00005\u00000\u0000.\u00000\u00000\u00000\u00000\u00000\u00000\u00000\u00000\u0000">]
CSV.read(path, {:headers => true, :encoding => 'utf-8'}).map(&:to_s)
=> ["\u00006\u0000e\u00005\u00004\u00009\u00001\u0000e\u00007\u0000-\u00007\u0000f\u00001\u00005\u0000-\u00004\u00001\u00007\u0000d\u0000-\u0000a\u00004\u00000\u00003\u0000-345\u0000,\u0000\u00005\u00000\u0000.\u00000\u00000\u00000\u00000\u00000\u00000\u00000\u00000\u0000\n"]
It's unfortunately still not the correct string :(
Final Solution (via #ashmaroli below):
path = Rails.root.join('data', 'mike/test-csv.csv')
csv_text = ''
File.open(path, 'r') do |csv|
csv.each_line do |line|
csv_text << line.gsub(/\u0000/, '')
end
end
CSV.parse(csv_text, headers:true).map do |row| row end
Result:
[#<CSV::Row "ID":"6e5491e7-7f15-417d-a403-345" "Quantity":"50.00000000">]
Github Gist
Download Example CSV File
path = Rails.root.join('data', 'mike/test-csv.csv')
file = ""
File.open(path, 'r') do |csv|
csv.each_line do |line|
file << line.gsub(/\u0000/, '')
end
end
print file
print file.inspect # same as above just wraps the string in a
# single line with "\n" chars
When exporting csv in Rails 4.2 app, there are ascii code in the csv output for Chinese characters (UTF8):
ä¸åˆåŒç†Šå·¥ç‰ç”¨é¤
We tried options in send_data without luck:
send_data #payment_requests.to_csv, :type => 'text/csv; charset=utf-8; header=present'
And:
send_data #payment_requests.to_csv.force_encoding("UTF-8")
In model, there is forced encoding utf8:
# encoding: utf-8
But it does not work. There are online posts talking about use gem iconv. However iconv depends on the platform's ruby version. Is there cleaner solution to fix the ascii in Rails 4.2 csv exporting?
If #payment_requests.to_csv includes ASCII text, then you should use encode method:
#payment_requests.to_csv.encode("UTF-8")
or
#payment_requests.to_csv.force_encoding("ASCII").encode("UTF-8")
depending on which internal encoding #payment_requests.to_csv has.
You can try:
#payment_requests.to_csv.force_encoding("ISO-8859-1")
for Chinese characters
CSV.generate(options) do |csv|
csv << column_names
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end.encode('gb2312', :invalid => :replace, :undef => :replace, :replace => "?")
This is what worked for me:
head = 'EF BB BF'.split(' ').map{|a|a.hex.chr}.join()
csv_str = CSV.generate(csv = head) do |csv|
csv << [ , , , ...]
#elements.each do |element|
csv << [ , , , ...]
end
end
Hi i want to read and write data from CSV and excel file both.Can anyone please help me that
which gem or plugin is more suitable for this.
ruby 1.9.x provides a nice and fast CSV class in Stdlib, see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/csv/rdoc/index.html.
For spreadsheets, I'd suggest http://spreadsheet.rubyforge.org/, or as already suggested, https://github.com/hmcgowan/roo
edit
I could dig out an example for csv from my code, this may help you to get started.
import:
CSV.foreach(params[:csv][:csv_file].tempfile) do |row|
# do something with your row ...
end
export:
#export = CSV.generate do |csv|
# adding header columns
csv << [
'Column 1',
'Column 2',
'Column 3' #, ...
]
#records.each do |record|
csv << [
record.name,
record.description,
record.created_at #, ...
]
end
end
# if you want to send the data directly to the client (works inside a controller action)
send_data #export, :type => 'text/csv; charset=utf-8; header=present', :disposition => 'attachment: filename=export.csv'
Can you try roo gem for excel
https://github.com/hmcgowan/roo
And for CSV
https://github.com/arydjmal/to_csv
I have a servlet (java) returning a csv file. So in my controller I send a post request,
def handleCsvRequest
response = RestClient.post theUrlPathTotheServlet queryParams
end
Now how do I handle the response so that it prompts the user to download this csv file. I know you can do this via a form and hidden Iframe but i'd like to do it through rails. I am looking through fastercsv but i am not finding great examples. Many thanks.
I have tried the following:
i have tried the following
csv_string = RestClient.post url, json, :content_type => :json
csv_file = CSV.generate do |csv|
csv << [csv_string]
end
send_data csv_file, :type => 'text/csv; charset=iso-8859-1; header=present', :disposition => "attachment; filename=report.csv"
but i don't get prompt for a file download? any ideas?
Do have a look at
1> http://fastercsv.rubyforge.org/ - For Documenation
2> http://supriya-surve.blogspot.com/2010/02/using-fastercsv-to-import-data.html - As an e.g.
Use send_file or send_data to send the csv data back to the browser.
A typical example of send_data is something along the lines:
csv_data = CSV.generate do
# block to generate CSV text
end
send_data csv_data, :filename => 'your_data.csv'
A typical example of send_file is
#csv_filename ="#{RAILS_ROOT}/tmp/your_data.csv"
send_file #csv_filename, :filename => "your_data.csv"
This should work in development. If this does not work in production, and you are using an Apache server,
you have to comment out the following line in config/environments/production.rb
config.action_dispatch.x_sendfile_header = "X-Sendfile"
Hope this helps.