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!

floriansuchan
  • 255
  • 4
  • 13

2 Answers2

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.

Jonas Witt
  • 66
  • 2
  • 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? – floriansuchan Sep 13 '14 at 16:27
  • 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. – Jonas Witt Sep 13 '14 at 17:09
  • 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? – floriansuchan Sep 13 '14 at 17:58
  • 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. – Jonas Witt Sep 13 '14 at 18:50
  • 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? – floriansuchan Sep 13 '14 at 18:50
  • 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. – Jonas Witt Sep 14 '14 at 09:29
  • 1
    found a blogpost on how to handle zip files with tempfiles: http://thinkingeek.com/2013/11/15/create-temporary-zip-file-send-response-rails/ Thanks for your help! – floriansuchan Sep 14 '14 at 17:43
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.

aquajach
  • 2,548
  • 2
  • 23
  • 29