Uploading multiple files with progress indicator using jQuery, Flash and Rails

I just implemented a new way of uploading assets such as photos and PDF-files to Lokalebasen.dk. There is nothing revolutionary about it, but I hit a few snags on the way, and I thought I’d share my choices here.

Lokalebasen (a Danish website for rental and sale of business property) is (of course) based on Ruby on Rails and uses the notoriously brilliant jQuery as Javascript framework. When the customer asked for a progress bar while uploading assets, I knew there was basically two choices: Polling the server for the progress of the upload, or uploading through Flash. I choose the last option because I believe it is easier to implement, and it gives the added bonus of being able to start the upload in an “ajaxy” way without refreshing the page.

Several ready-made solutions exists, and chose one that was built as a jQuery plugin, was updated recently and was easy to use while being highly configurable: Uploadify. This article is not an Uploadify tutorial – you’ll have to work the out from the documentation and the examples. Rather, it’s about the last piece of the puzzle, how to make Rails play nicely with Uploadify.

Lokalebasen uses attachment_fu for handling assets. Since traditional file upload was already implemented, I had fallback functionality for users without Javascript or Flash – and so should you. All I had to add the controller handling the upload was a detection of the correct content type. Browsers will usually provide this, but Flash does not. And so the controller ended up looking something like this:

The mime-types gem is used to detect the content type – installation and usage is explained here.

I needed the functionality several places, so I wrote a partial I could reuse:

There is a lot going here, so let’s take it from the top. The content_for :head section is the code that will be placed inside the <head&gt part of the page – my layout takes care of that with a yield :head call. So first the Uploadify jQuery plugin javascript file is included, followed by some javascript that applies Uploadify to the file_uploader div seen later in the partial. This includes a lot options, some of which uses variables supplied to the partial. Here is an example of how I call the partial:

I won’t walk you through all the options I supply for Uploadify, but let’s take a look at the important ones: script is where Uploadify will post the uploaded file to. This should be the create action of your asset controller. scriptData is the most tricky one. The option specifies what parameters should be posted to the controller along with the file. 'format': 'json' ensures that the wants.json block is invoked in the controller, instead of the default wants.html. This helps the controller to separate Flash uploads from ordinary uploads. The two other parameters in scriptData will be explained later in the article, is they are the key to get the uploading past security and authentication measures taken. fileDataName extracts the name to use for the uploaded file (e.g. asset[:uploaded_data] for attachment_fu) directly from the fallback form. onComplete makes an ajax get request through jQuery to the same url we are currently on. I use this to render some javascript that updates the page in a wants.js in the controller.

There are several gotchas when you upload files through Flash. The most common one, which also apply to ajax, is the infamous ActionController::InvalidAuthenticityToken exception. You will get this exception on any default Rails installation with authenticity checking enabled. Rails expects any post to an action the include the authenticity_token parameter. It is used to verify the post actually came from the same application, and Rails automatically adds to all the forms and ajax requests it generates. In this case, we have to apply it manually, and this what happens with 'authenticity_token': encodeURIComponent('<%= form_authenticity_token if protect_against_forgery? %>'). The form_authenticity_token returns a valid token, but first we check if forgery is enabled. If it is disabled (which it usually is in tests), we will get an error if we invoke form_authenticity_token. encodeURIComponent makes sure that any characters in the token is encoded correctly. With this parameter, we should be able to make an authentic post through Flash – or maybe not …

Rails use data in the user session to authenticate requests, and requests directly from Flash does not include the session cookie that Rails use to find the session. Thus we still get the ActionController::InvalidAuthenticityToken exception, even with the authenticity_token parameter added. We have to make a slight hack into how Rails handles sessions to make this work. Place this code in a file in config/initializers to apply the hack, which tells Rails to try to read the session id from a parameter, if it can’t find in a cookie. This will only happen, however, if we add the line session :cookie_only => false, :o nly => :create to the asset controller. Also, we must supply the session in a parameter, which is what '<%= Rails.configuration.action_controller.session[:session_key]%>': '<%= u session.session_id %>' } do. The unique session key of the application is taken from the Rails configuration, and the session id is taken from session.

With these measures in place, Rails can now properly authenticate our Flash upload request as a legal, secure post. As an added bonus, actions protected behind session-based logins now also just works. And I would guess that most applications require their users to register and login before they can upload files.

Finally, here is a trick if you use http basic authentication e.g. for the administration tool like we do on Lokalebasen.dk. Place an is_admin? flag in the session, as shown in the code below. This will allow even Flash uploads to be authenticated, even if they don’t supply http basic authentication information:

13 thoughts on “Uploading multiple files with progress indicator using jQuery, Flash and Rails

  1. Great post.
    Good insight into possibilities concerning user experience.
    Uploading and contributing is all the rave :-) – easy, cheap and beautiful implementation is the key for being able to apply uloading solutions over a wide array of applications..

    And here is the reason I commented:
    You gotta a love the language :-)


    end
    end
    end
    end

    end

  2. Are you being ironic, Rasmus? :)

    Btw, you can write } } } } } instead of all the ends if you prefer – Ruby allows for both notation forms. But you’d probably rather not have to write anything at all – but where would that leave the poor compiler? ;)

  3. Not ironic when commenting on UX possibilities and as you pointed out, I’m not in a position to discuss syntax or semantics of a high level programming language. :D

    I just liked the:
    end
    end
    end
    end

    end

    keep up the good work.

  4. Nice article, thanks for your work !
    Unfortunately, I came through a weird bug with “special” characters (such as ‘+’) in the form authenticity token. On the server side, when I get the token, ‘+’ characters for instance are removed. I’m pretty sure it’s caused by the flash component since I use encodeURIComponent.
    Have you met this bug before ?

  5. Thanks Casper, I took a look at the action script code and I saw a call to the “unescape” method on the scriptData variable (used by the uploadify query). Perhaps, they changed it for the current reelase. So, next step: remove this call and re-compile the flash component.

  6. @Did
    i have the same problem.
    i use CGI.escape for convert authenticity_token
    it’s work very well when you have a ‘+’ in your token

  7. Thanks for this! Your method, combined with Rob Anderton’s post using a custom Rack middleware worked out great.

    Unfortunately, I’m not exactly sure why, but I had a couple problems (Rails 2.3.2), the first being that I needed to replace Rails.configuration.action_controller.session[:session_key] with ActionController::Base.session_options[:key] (Rails complained that the first session variable was nil), and the second being that I have some discrepancies in my session IDs from session.session_id.

    session.session_id gives me an ID something like “2d1c06c075ecd8d7e3d8e2adf2371f06,” which seems reasonable, since as far as I know session IDs are supposed to be 32-byte hashes, but the actual session IDs I get from working form posts are more like, “BAh7CToMdXNlcl9pZGkGOg9zZXNzaW9uX2lkIiUyZDFjMDZjMDc1ZWNkOGQ3ZTNkOGUyYWRmMjM3MWYwNjoQX2NzcmZfdG9rZW4iMWdYRGQ0U29SU1k2NGJuOEY1MWZ6S1U3ZW1ZL0hPR09lVU0wTVUvQWQwbzQ9IgpmbGFzaElDOidBY3Rpb25Db250cm9sbGVyOjpGbGFzaDo6Rmxhc2hIYXNoewAGOgpAdXNlZHsA–e96c3614fb16e404a2e1cbcbaf7a401d97883bbf”

    I really don’t have any clue why this happens, as I’m pretty new to Rails, but I was able to fix the problem by using request.cookies[ActionController::Base.session_options[:key]] instead of session.session_id.

    Hope this helps anyone having a similar problem.

  8. I probably should have thought about this before posting–that’s my entire session, and as I’m storing a great deal extra information in it, I guess it’s important to send the whole thing.

  9. Hi Brandon,

    Thank you for your comments – and don’t worry about the design of the blog ;)

    I don’t mention it in the article, but that code was made a Rails 2.1.2-based project. There has been made significant changes to how sessions work in Rails 2.3.2, so that’s probably what gave you trouble. I’m glad to hear you still got it working, though – I’ll soon need to use this technique on a 2.3.2 project myself, and I’ll post a followup if I run into the same trouble.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>