Creating multiple csv-files and download all in one zip-archive using rails
Asked Answered
E

2

10

I am looking for a way to create multiple csv files and download them as one zip archive within one request in my rails application.

To build the archive I use rubyzip gem - to download it just the rails built-in function send_data. The problem I have is that rubyzip's add-function requires a pathname to load files from. But there is no path as my csv files are created within the same request.

Some Code:

# controller action to download zip
def download_zip
  zip = @company.download_all
  send_data zip, filename: "abc.zip", type: 'application/zip'
end

# method to create zip
def download_all
  Zip::File.open('def.zip', Zip::File::CREATE) do |zipfile|
    self.users.each do |user|
      #some magic to combine zipfile.add() and user.to_csv
    end
  end
end

# method to create csv
def to_csv
  CSV.generate do |csv|
    #build awesome csv
  end
end

Is there a way to save my csv files temporarely at some directory, that I can pass a pathname to zipfile.add()?

Nice weekend everybody and happy coding!

Egor answered 13/9, 2014 at 15:2 Comment(0)
S
5

You could either write your CSV output into a temporary file and call zipfile.add() on that, but there is a cleaner solution:

zipfile.get_output_stream("#{user.name}.csv") { |f| f.puts(user.to_csv) }

See http://rdoc.info/github/rubyzip/rubyzip/master/Zip/File#get_output_stream-instance_method for more details on get_output_stream - you can also pass additional parameters to specify attributes for the file to be created.

Schismatic answered 13/9, 2014 at 15:40 Comment(7)
that seems to be the method I was looking for! but something is still missing: opening myarchive.zip creates a new file called myarchive.zip.cpgz is there still something wrong with creating the archive or is that another problem maybe with my mac?Egor
Have you tried specifying the mime type? send_data zip, filename: "abc.zip", type: 'application/zip' - many browsers/OSes will only treat the file correctly if that is present.Schismatic
yes, I specified the mime type. I accidentially opened abc.zip with my editor. turns out it contains all user entries in an array, not the actual csv files I want to build for every user. to_csv for users does work, as I use it somewhere else to provide a csv download for a specific user. is there something missing in the puts block? maybe another type specification?Egor
That's because you're assigning the result of download_all to the zip variable. The result of download_all is the array of users, as that is the last statement in that method. The first argument of send_data should be the contents of the zip file. You should check out the tmpdir library to create a proper path to store your zip file in (not just "abc.zip", which is relative to your app dir). Then read that file and pass its contents to send_data.Schismatic
ok I think we are pretty close now: as you can see I changed the directory in download all to def.zip. the request creates that directory in my application directory. unarchived it correctly contains all my csv files. but why is it created in my app directory and the one I download is somehow broken?Egor
You should not create that zip file in your application directory, it's bad practice. Ideally, you should return the full path to the zip file in a temporary location from download_all. Then you can utilize that full path in your controller method.Schismatic
found a blogpost on how to handle zip files with tempfiles: thinkingeek.com/2013/11/15/… Thanks for your help!Egor
P
4

get_output_stream doesn't work for me. However, the updated method Zip::OutputStream.write_buffer helps

https://gist.github.com/aquajach/7fde54aa9bc1ac03740feb154e53eb7d

The example adds password protection to the file as well.

Psid answered 22/4, 2016 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.