Using rubyzip to create zip files on the fly

January 21, 2008 by Michael

In my Daily Fratze project the users should be able to download their faces as a zip file backup.

Until now they have been able to upload zip files. For that, i used rubyzip which worked quite well.

As a starting point i found a nice article on the joy of rubyzip, but this has a major flaw for me. It uses the Zip::ZipFile interface to create its archives. This interfaces takes a filename as parameter and either creates this file if it doesn’t exists or tries to open it as a zip archive.

I doesn’t want my directories polluted by some random zip files so i tried to use TempFile. Creating a new TempFile leads to an existing file which Zip::ZipFile cannot open.

My solution uses the more basic interface Zip::ZipOutputStream. Further requirements were adding binary files with arbitrary names and not like in the examples of rubyzip, creating new files with some textual content. Here we go:

require 'zip/zip'
require 'zip/zipfilesystem'
 
t = Tempfile.new("some-weird-temp-file-basename-#{request.remote_ip}")
# Give the path of the temp file to the zip outputstream, it won't try to open it as an archive.
Zip::ZipOutputStream.open(t.path) do |zos|
  some_file_list.each do |file|
    # Create a new entry with some arbitrary name
    zos.put_next_entry("some-funny-name.jpg")
    # Add the contents of the file, don't read the stuff linewise if its binary, instead use direct IO
    zos.print IO.read(file.path)
  end
end
# End of the block  automatically closes the file.
# Send it using the right mime type, with a download window and some nice file name.
send_file t.path, :type => 'application/zip', :disposition => 'attachment', :filename => "some-brilliant-file-name.zip"
# The temp file will be deleted some time...
t.close

Edit: The basename given to Tempfile.new is what the name says: A basename. It doesn’t need to denote a full path. Tempfile creates an arbitrary path for you in the default temporary directory.

11 comments

  1. Mike wrote:

    great article..funny that the day i wanted to do this you posted this article. one question though, i am using the x_send_file plugin to send my user’s files, and in one of my actions (not using rubyzip&tempfile) it works fine, but when i go to change your send_file line (which works) to an x_send_file it doesnt work (200OK 404 error)..any ideas? are there any specifics to the Tempfile that require some sort of wait between the creation and the sending or something? thanks again

    Posted on January 22, 2008 at 8:47 AM | Permalink
  2. Michael wrote:

    Hi Mike, sorry, i don’t know about the x_send_file plugin. I always used send_file.

    If you have a look at dailyfratze.de, the images of my users are also send through send_file as they are non public images and i didn’t want to hack some htaccess stuff to prevent them from being seen so i serve them from rails. What i want to say: i didn’t have a need for another plugin, the send_file works great for me.

    Mike: I digged into x_send_file and find it of great value to serve my images. I didn’t know that it was possible to let Apache or Lighttpd handle the load of files that need to be processed by the rails app. This plugin is great! I myself use Apache as a frontend to my mongrels. To enable Apache to server files that aren’t in the public path of your webapp, you need to add the following to your host, vhost or .htaccess conf after installing mod_xsendfile

    # Turning XSendFile on
    XSendFile on
    # Enabling it to serve arbitrary files
    XSendFileAllowAbove on

    With this config it is possible to serve the temporary file from my orignal post.

    Have a nice day and thank you for visiting 🙂

    Posted on January 22, 2008 at 9:24 AM | Permalink
  3. Mike wrote:

    hey michael…yeah no problem about the x_send_file…i was having problems with it (as you saw above) but for some reason i was not able to x_send_file stuff that was in /tmp or /var/tmp. mightve been some security precaution on my server or something but anyway once i changed the directory the Tempfile was created the rubyzip+x_send_file worked like a charm

    Posted on January 24, 2008 at 5:00 AM | Permalink
  4. Kamal Ranaweera wrote:

    Works Great!!
    Thank you so much.

    Posted on February 7, 2010 at 10:16 AM | Permalink
  5. Ze Maria wrote:

    Nice article, however I did have to replace:
    zos.print
    with
    zos.write

    If not, the lib would escape the content

    Cheers

    Posted on November 10, 2010 at 6:58 PM | Permalink
  6. radu wrote:

    Tried to do this thing, however, in the line of zos.print IO.read(file.path)
    I got error of undefined method `path’ for #
    Do you need to create a “path” column in the sql? What If I do not have the path? I tried to change it into file.name (because I have name column in sql), but it returns error of
    No such file or directory – myfilename

    Thank you for any pointers

    Posted on March 8, 2011 at 9:18 AM | Permalink
  7. Michael wrote:

    Radu: The instance “file” denotes a model of mine that has a method “path”. path returns the fully qualified filename of that file, i.e. “/my/files/file123.jpg”. The method does not need to be called path, you can name it whatever you like but “IO.read()” takes the fqn path of an existing file.

    For me it seems like you have a relative filename in your database.

    Posted on March 8, 2011 at 9:25 AM | Permalink
  8. ampedandwired wrote:

    Nice, thanks for this. Here’s a slight variation on the them showing zipping of any arbitrary directory into memory using ZipOutputStream::write_buffer.

    https://gist.github.com/3385002

    Posted on August 18, 2012 at 9:08 AM | Permalink
  9. levitra wrote:

    Admiring the hard work you put into your website and detailed information you present.
    It’s good to come across a blog every once in a while that isn’t the same out of date rehashed information.

    Excellent read! I’ve bookmarked your site and I’m including your RSS feeds to my Google account.

    Posted on April 26, 2013 at 7:04 PM | Permalink
  10. Stefano wrote:

    I love you.

    Posted on December 17, 2018 at 11:20 PM | Permalink
  11. Michael wrote:

    😀

    Posted on March 15, 2019 at 9:16 AM | Permalink
5 Trackbacks/Pingbacks
  1. Learnings for Jan 4th « Rubinauts Blog on January 4, 2011 at 7:44 PM

    […] you want to work with something like a Tempfile as well as files, use ZipOutputStream instead. See http://info.michael-simons.eu/.....n-the-fly/ for an […]

  2. […] Using rubyzip to create zip files on the fly […]

  3. […] Using rubyzip to create zip files on the fly […]

  4. […] http://info.michael-simons.eu/.....n-the-fly/ […]

  5. […] http://info.michael-simons.eu/.....n-the-fly/ […]

Post a Comment

Your email is never published. We need your name and email address only for verifying a legitimate comment. For more information, a copy of your saved data or a request to delete any data under this address, please send a short notice to michael@simons.ac from the address you used to comment on this entry.
By entering and submitting a comment, wether with or without name or email address, you'll agree that all data you have entered including your IP address will be checked and stored for a limited time by Automattic Inc., 60 29th Street #343, San Francisco, CA 94110-4929, USA. only for the purpose of avoiding spam. You can deny further storage of your data by sending an email to support@wordpress.com, with subject “Deletion of Data stored by Akismet”.
Required fields are marked *