Uploading Files With SWFUpload
Update: this can work with cookie-based sessions: see Dylan Vaughn’s comments below.
Another update: this gets better and better! Nathan Colgate has put together a screencast showing how to hook up Rails 2.0.2, attachment_fu, SWFUpload and RESTful authentication. See his comments below.
It’s About The Tea
Sometimes it’s useful to be able to upload lots of files to your webapp in one go. Rather than sitting at your computer uploading one photo after another, just queue them all up and wander off for a nice cup of tea instead.
SWFUpload is a Flash app that lets you have more nice cups of tea. It also lets you filter file types and sizes, displays progress bars and is fully styleable with CSS.
Getting SWFUpload working in your Rails app isn’t rocket surgery. But it would have been quicker for me if the various tips scattered around the web had all been in one place. So here they are.
The Warm Up
Before you start tinkering with SWFUpload, get normal HTML file uploads working. This proves that your server-side code works and lets your app degrade gracefully should the user have Flash or Javascript disabled.
Configuration Over Convention
SWFUpload is a Flash/JavaScript library and isn’t tailored to Rails. There are many configuration options and it isn’t immediately clear which of them are necessary — especially to those used to convention over configuration.
Happily the gallic Flornet has a minimal demo Rails app with SWFUpload you can download and pull apart. Poking around with this helped me quickly work out what was important and what wasn’t.
Getting With The Session
Unfortunately, with out-of-the-box SWFUpload, Rails can’t pick up the user’s session. Flash 8 doesn’t send meta data with the uploaded files so Rails doesn’t know which session to load. There’s also no way to add a hidden field to the multipart form data.
The solution comes in two parts. This won’t work as is with cookie-based sessions, Rails 2’s default session store — but it will work with Dylan Vaughn’s modifications (see comments).
First hack Ruby’s CGI::Session class like this.
Second, append your app’s session key and value to the upload_script argument in the SWFUpload constructor. Mine looks like this:
upload_script: "<%= assets_path @client %>?_photocms_session_id=<%= session.session_id %>"
It’s About The Callbacks
Maybe it’s just me but I didn’t grasp at first the significance of SWFUpload’s callback architecture and ability to upload multiple files. In my Rails action I wanted to redirect to a new page once all the files were uploaded and I couldn’t understand why SWFUpload was complaining about the 302s.
Eventually the penny dropped. SWFUpload calls your action once per file, not once for all the files, and so the action must simply return a 200 status code. Anything else will interfere with SWFUpload before it’s finished. Here’s how my action looks now:
def create
# HTML file upload
if params[:asset]
@asset = @client.assets.create! params[:asset]
flash[:notice] = 'Successfully uploaded asset.'
redirect_to client_path(@client)
# SWFUpload file upload
elsif params[:Filedata]
@asset = @client.assets.create! :swf_uploaded_data => params[:Filedata]
render :nothing => true
end
end
So the way to react to uploaded files is to code up the uploadFileComplete(file) and uploadQueueComplete(file) JavaScript callbacks.
Around the time my brain cell got into gear, Peter De Berdt kindly explained it all in detail.
Missing MIME Type
Unfortunately Flash 8 sends malformed MIME type data to the server. This means that the content type is always set to application/octet-stream which in turn means attachment_fu, for example, won’t resize images (because it doesn’t know they are images).
My answer was to use the MIME::Types gem to deduce them. Install it like this:
$ sudo gem install mime-types
I then added an swf_uploaded_data= method to my model, based on attachment_fu’s uploaded_data= method. Here it is:
def swf_uploaded_data=(file_data)
return nil if file_data.nil? || file_data.size == 0
# Map file extensions to mime types. Thanks to bug in Flash 8
# the content type is always set to application/octet-stream.
self.filename = file_data.original_filename if respond_to?(:filename)
mime = MIME::Types.type_for(self.filename)[0]
self.content_type = mime.blank? ? file_data.content_type : mime.content_type
if file_data.is_a?(StringIO)
file_data.rewind
self.temp_data = file_data.read
else
self.temp_path = file_data.path
end
end
Voilà — the content type sorts itself out.
Plugins That Make This Easier
Having hacked my way this far I suddenly started finding various plugins that do most of this for you. I’m sure they weren’t there when I looked the first time. I haven’t tried any of them but you may wish to:
For attachment_fu and MIME types: mimetype_fu (see Thomas’s comment which pushes the MIME type deduction into attachment_fu).
For SWFUpload and attachment_fu: ActiveUpload.
Conclusion
SWFUpload is a really nice way of improving the file upload experience for users. Flash 8 has a few hitches but none is intractable. And if it all goes pear-shaped, you can fall back to plain old HTML — which isn’t so bad anyway.
Posted in Rails

40 Comments
Jump to comment form