Dec 21, 2008

Rails and Processing Uploaded .zip Files

I'm doing a little side project at the moment that allows a user to upload .zip files and the Rails app will process the contents. It turns out to be quite a pain in the ass to get going! The main reason is that the Ruby gem that handles .zip files only works with files, and with Rails you're not actually guaranteed to get a File object when someone uploads a file to you.

Let's start with the basics. Suppose you have an action like this:
def upload_to_me
file = params[:the_file]
end
Assume that the_file is an item uploaded from a form in a file input field. Now Rails will automatically process all this for you and handle the temp file creation and all that. However one optimization Rails will do is if the file is smaller than 10kB, it just sticks it in an UploadedStringIO object which is not a file - so there is no temporary file.

Let's expand our action. We want to open up this file (assume it is a .zip) and take a peek at the contents:
Zip::ZipFile.open(file.path) do |zip|
end
The ZipFile object only accepts a filename. There is no way for you to pass in anything else, like say an IO object. So we have a predicament. The UploadedStringIO object we have is raw zipped data, but we can't actually unzip it because it is not a file.

What's the solution? It's ugly, but turn it into a file:
if file.is_a?(UploadedStringIO)
temp_file = Tempfile.new("some_temp_name")

temp_file.write file.read
file = temp_file
temp_file.close
end

# now file is a File object and can be treated as such
We use Ruby's Tempfile object, which stores things in a temporary folder (by default on Ubuntu it appears to be /tmp) and is designed to be thread-safe so that you don't have to worry about people clobbering each other's temp files.

I suppose since I have access to both Rails' code and the Zip gem's code, I could probably hack this stuff to make it work properly without being ugly, but this small fix should be enough for now. A good optimization would be to add something to ZipFile so that it can accept a IO object and not just a filename.

4 comments:

CAA said...

Just to let you know, your post helped me realize that I can pass ImageScience the Rails Temp File directly.

ImageScience.with_image(uploaded_image.path) do |img|

Rob Britton said...

Glad to be of help! But remember the little gotcha I mentioned, if the file is small then Rails will not actually save it to a file. The IO class doesn't actually have a path method so your code will crash on files under 10kb.

katch wreck said...

thank you! i was having this exact same problem with my site. cheers to your helpful code snippet! i was starting to get stressed out ha ha but now i'm happy again :-) i owe you!

andredurao said...

Thanks for the help
I were looking for how to write the UploadedStringIO data into a file, It was so easy just run "read", but rails documentation is so poor, sometimes you dont find nothing.