In a Ruby on Rails application I’m developing, client asked me to add a feature that allows users to download a full picture gallery as a single zip file.

Quite obviously, I decided to take advantage of rubyzip gem to create the compressed archive to download.

But my idea was to create the zip on the fly, directly in memory, without fill folders of my application with many zip files that I need to remove later.

Hence the idea of using Tempfile to create the file in memory.

Here’s the function I created: “download_zip“.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def download_zip(image_list)
    if !image_list.blank?
      file_name = "pictures.zip"
      t = Tempfile.new("my-temp-filename-#{Time.now}")
      Zip::ZipOutputStream.open(t.path) do |z|
        image_list.each do |img|
          title = img.title
          title += ".jpg" unless title.end_with?(".jpg")
          z.put_next_entry(title)
          z.print IO.read(img.path)
        end
      end
      send_file t.path, :type => 'application/zip',
                             :disposition => 'attachment',
                             :filename => file_name
      t.close
    end
  end

This is what the download_zip function does:

  • takes as input a list of Image objects, where Image has at least the two properties: title and path.
  • create a temporary file and open it as a ZipOutputStream.
  • for each image in the list creates a new element in ZipOutputStream with the name equal to the image title and content is the image read from path.
  • the close ofthe block Zip::ZipOutputStream … end will cause the new file zip will be automatically closed.
  • now, the file is sent to the user, with the right mime-type set.
  • the last instruction close the temporary file that will be removed from memory later by the garbage collector.

As you can see, with a few lines of code you have gotten a very clean and effective solution.

About Claudio

Claudio Marai is a co-founder of DevInterface.

After graduating in Computer Science has contributed to develop complex web applications based on Java/J2EE and desktop applications with the. NET framework for the Ministry of Justice and ultimately for the banking ambit.

The passion for web in recent years has led him to be interested in more modern frameworks such as Ruby on Rails and Django, and to a development approach based on agile methodologies such as eXtreme Programming and SCRUM.

About DevInterface

We are an information and communication technology agency.

Our mission is to provide web application development, design services and communication strategies.

We specialize in building web applications with modern and efficient frameworks.

Related Post

Comments (10)

  1. ramzi says:

    Tempfile.new(“my-temp-filename-#{request.remote_ip}”)

    using the client ip address to generate a unique file is flawed…

    what happens if a user has more than one browser open, trying to download pictures?
    also, keep in mind that in many scenarios (universities, large corporations, fastweb), large subnets access the internet using a single IP address.

    a better solution would be http://raa.ruby-lang.org/project/ruby-uuid/

    keep up the great stuff!

  2. Claudio says:

    Your observation is correct. Thank you for letting me see this “unforgivable” mistake. I’ve adjusted the example using a simple but effective workaround:

    Tempfile.new ( “my-temp-filename-request.remote_ip # ()”)

    now becomes

    Tempfile.new ( “my-temp-filename-# (Time.now)”)

    so that the name is no more linked to the ip-address but rather to the download-time.

  3. Excellent tip, I will be using this for my website! My only comment is that it is still possible for two people to request zip files at the same time (Time.now is accurate to the second), so I might either use a UUID (as Ramzi suggested), or just add a random number, like so:

    Tempfile.new(“my-temp-filename-#{Time.now.to_s + rand(9999).to_s}”

    Also, since the files exist only in memory, and therefore likely only last a short amount of time, Time.now provides very little entropy. Just a thought!

  4. Tonya Henson says:

    You can also go to open zip file to open your zip files online. I hope it would be a great help.

  5. Claudio,

    I’ve got this error when using your code at line 12:

    wrong argument type String (expected Zip::Archive)

    11. t = Tempfile.new(“my-temp-filename-#{Time.now}”)
    12. Zip::ZipOutputStream.open(t.path) do |z|

    What am i doing wrong?

  6. Claudio says:

    Hi Ricardo,
    I’ve tried to reproduce your error without success.

    May be you’ve not included zip gems in your controller?

    Try with with:
    require ‘zip/zip’
    require ‘zip/zipfilesystem’

  7. Steve says:

    I’m getting all sorts of errors for this, namely that the images come out in the compressed folder corrupted, with bad filetypes and defintions.

    All of the zip creation and filenaming goes well, but when the file has been downloaded, the files just refuse to show due to either being corrupted (through ASCII conversion) or have bogus Huffman definitions which flag them as Qt-PICT exploits.

    Anyone else run into this issue?

  8. David says:

    Hey Steve, I am running into the same problem. I am on OS X running Ruby 1.9.1 .. whenever i try to unzip the resulting file Unarchiver tells me there is an error decrunching the file…

  9. saranya says:

    how to unzip an image file in rails3

  10. This worked great for me under 1.8.7 but after upgrading to Rails 3 and Ruby 1.9.2 I started running into the same problems as Steve. I suggest you give http://zipruby.rubyforge.org/ a try if you are having issues.

Leave a Reply

Insert code beetween <code lang="ruby"> and </code>