How to unzip a file in Ruby on Rails?

40,874

Solution 1

I'd use the rubyzip gem. Specifically this part: https://github.com/rubyzip/rubyzip/blob/master/lib/zip/filesystem.rb

It creates an artificial file system in memory mirroring the contents of the zip file. Here's an example based of the example from the docs:

Rubyzip interface changed!!! No need to do require "zip/zip" and Zip prefix in class names removed.

require 'zip'

Zip::File.open("my.zip") do |zipfile|
  zipfile.each do |file|
    # do something with file
  end
end

In your case, just put the name of the uploaded tempfile where my.zip is in the example, and you can loop through the contents and do your regular operations on them.

Solution 2

From the RubyZip project page:

Rubyzip interface changed!!! No need to do require "zip/zip" and Zip prefix in class names removed.

So, the example code from @ben-lee should be updated to something like this:

require 'zip'

Zip::File.open("my.zip") do |zipfile|
  zipfile.each do |file|
    # do something with file
  end
end

Solution 3

Extract Zip files in Ruby

Once you've installed the rubyzip gem, you can use this method to extract zip files:

require 'zip'

def extract_zip(file, destination)
  FileUtils.mkdir_p(destination)

  Zip::File.open(file) do |zip_file|
    zip_file.each do |f|
      fpath = File.join(destination, f.name)
      zip_file.extract(f, fpath) unless File.exist?(fpath)
    end
  end
end

You use it like this:

extract_zip(zip_path, extract_destination)

Solution 4

Worked for me:

gem install rubyzip

main.rb

require 'zip'

def extract_zip(file, destination)
  FileUtils.mkdir_p(destination)

  Zip::File.open(file) do |zip_file|
    zip_file.each do |f|
      fpath = File.join(destination, f.name)
      FileUtils.mkdir_p(File.dirname(fpath))
      zip_file.extract(f, fpath) unless File.exist?(fpath)
    end
  end
end

extract_zip('file.zip', 'tmp')
Share:
40,874
siamii
Author by

siamii

Updated on July 29, 2022

Comments

  • siamii
    siamii almost 2 years

    I'm uploading a file to the server in Ruby on Rails

    Normally, it's a text file and I save it in the model as a 'file' field in a Submission ActiveRecord with other fields such as title of submission.. etc.

    However, the user can also submit a zip file. In this case the zipfile should unzipped and for each file in the zip a new Submission should be created with the same text fields, but current file.

    How can I accomplish this?

    I looked at unzip examples on the net, but most use a directory to unzip the files to. I'm not sure if I need that as in the current create method of SubmissionsController, a file object is received and I presume the path to save the file to is automatically generated when the Submission save method is called. So I was thinking that maybe I should unzip the zipfile in memory to get an array of file objects and then create a new Submission with each file object but same fields and then let ActiveRecord generate the file paths for each one when it saves them to the database. I might be wrong here, because I'm kind of new to Rails and Ruby.

  • siamii
    siamii about 12 years
    I see, so how could I get an array of file objects from the zip file?
  • Ben Lee
    Ben Lee about 12 years
    @bizso09, I just updated to show how to loop through the contents. There is also a simple tutorial explaning it here: markhneedham.com/blog/2008/10/02/…
  • siamii
    siamii about 12 years
    Unfortunately file is of type ZipEntry and not of type File, so I cannot save it with Submissions
  • Ben Lee
    Ben Lee about 12 years
    @bizso09, see the docs and the exmample. You can extract the file from the ZipFile, like zip_file.extract(f, f_path) unless File.exist?(f_path)
  • 0112
    0112 about 7 years
    I don't know if it's been changed recently or not, but the extract method only takes one arg, and will throw an error if you give it two.
  • Jon Burgess
    Jon Burgess over 6 years
    What's the reason behind the condition unless File.exist?(fpath)? Is that just to replicate behaviour of other tools?
  • Akash Agarwal
    Akash Agarwal almost 5 years
    @JonBurgess seems like it's to avoid replacing an existing file with the same name.