1. ActiveUpload - Easy File Uploads in Rails

    Introduction

    Our goal was to create a Rails plugin that allows the user to upload multiple files to a website, quickly and easily. This plugin should also give the developer control over what is uploaded, such as maximum filesize allowed, and what filetypes are acceptable. To accomplish this goal, we created the ActiveUpload project.

    After researching some alternatives, we settled on a Flash application called SWFUpload. SWFUpload is a Flash movie that was written using FlashDevelop. It is a Flash movie that runs on the client, and handles the client end of the uploads. However, the movie itself is invisible, it is styled with HTML and works via a series of JavaScript callbacks. It is simple yet flexible for the developer, and the user interface has all the functionality we require.

    SWFUpload is licensed under the MIT license and Flash Develop is free. For a demonstration of SWFUpload, visit their site.

    Design

    We need to design a Rails plugin that integrates SWFUpload with a Rails application. Our plugin will consist of three parts.

    • A Model for storing the files meta data in the database.
    • A Controller for accepting the uploaded files and manipulating the model.
    • A FormBuilder extension for rendering the User Interface.

    The Model

    We started with the model. We knew we would need to store basic information about the file, such as filename and size. We also knew that we would want to attach this model to other models. We decided on a single table, single model design, which stores the filename and size, and attaches to other models using a Polymorphic Association. The migration script looks like:

    and the model looks like:

    The Controller

    Next we decided to tackle the controller. The attachments controller has to do three things:

    • Upload the file
    • Create the associated entry in the database
    • Allow a user to download the file

    After many attempts at getting the controller to do this, we settled on a controller with three methods.

    • create
    • upload
    • show

    You are probably wondering why create and upload are separate methods. The short answer is, if you create the attachment model inside of upload, there is no way to get the newly created id back out to the web page where you will be creating (or updating) one of your models that has attachments. The long answer, well if you want the long answer, read the “Modifying SWFUpload” section.

    It is remarkably easy to accept a file upload inside of rails, all you have to do is:

    The full controller looks like:

    The FormBuilder

    So now we have a model to store the uploaded files meta data and we have a controller to accept the uploaded file and manage the model. Now we need a way of displaying the UI, that is the job of the FormBuilder.

    The FormBuilder is a little too big to show here, but I encourage you to look it over, you can find it here.

    Basically the FormBuilder write the HTML for you, that HTML includes:

    • The hidden input containing all the ids of the attachment models associated with this model
    • The visible list of uploaded files (with delete javascript)
    • The JavaScript to display SWFUpload

    The most complicated part of the helper is the JavaScript to display the Flash application, it looks like:

    That’s it, we now have all the pieces we need to accept uploaded files and attach them to any model. Normally I would jump directly into how you use it however, this project had a particularly interesting side note that I want to delve into. Please feel free to skip to the “Using ActiveUpload” section if you just want to get moving.

    Modifying SWFUpload

    As I alluded to in “The Controller” there are some limitation within SWFUpload. Ideally, SWFUpload would call its upload script and that would be it, that script would take care of creating the model, uploading the file and associating the file to the model you are actually working on. However, since the files are uploaded before you save the model that is impossible. The next best thing would be for the upload script to create the model and upload the file, then return the newly created id to the web page for associating when you save your model. Unfortunately, there is no way to get information from the upload script back into the page.

    Luckily, SWFUpload has numerous callbacks. We settled on a method by which we create the model in the call back that is called directly before you upload the file, the function is called uploadFileStart. This works great, except there is no way (that I could find) to get information from that callback into the upload script. Essentially, we could either associate the file with the attachment model, or the attachment model with your model, but not both.

    The solution was to modify the way SWFUpload works. As I mentioned in the Introduction, SWFUpload is open source and FlashDevelop is free, so anyone can modify the source.

    We examined the code and found where the call back was called:

    You can see the ExternalInterface is what allows SWFUpload to call JavaScript functions. We changed it to the following:

    We wanted to keep the solution as generic as possible, so we simply took the result of the callback (if any) and appended it to upload script. So we created a JavaScript call back that returned “?id=XXX” and we were able to get the id of the attachment model that was created in the call back into the upload script. The call back looks like:

    Using ActiveUpload

    There are six fast and easy steps to using ActiveUpload.

    First install the plugin:

    Second, generate the migration, model and controller, and migrate your database:

    Third, add the following lines to any model you wish to attach files to:

    Fourth, add the form helpers to your new and edit views:

    ]]>

    Fifth, add the view helper to your show view:

    ]]>

    Sixth and lastly, add the JavaScript and Stylesheets to your layout:

    You should now be able to upload files to your Rails application.

    ActiveScaffold

    ActiveUpload works well with ActiveScaffold. To get ActiveScaffold to render the correct upload forms you must create a partial form override. Luckily, ActiveUpload comes with one. In the plugin’s public directory ($PROJECT/vendor/plugins/activeupload/public/) you will find a file named _attachment_id_form_column.rhtml. Copy that file to your models views directory ($PROJECT/app/views/$MODEL/).

    Since attachment_id is a virtual column, you will need to tell ActiveScaffold to use it, you can do that by adding the following code to your model’s controller:

    Finally, you should add the following lines to your attachments_controller.rb:

    Notes

    You will want to periodically cleanse your Attachments table and the associated files. There are two different ways entries can get stuck in there.

    • A user adds attachments, then fails to save thier model
    • A user disassociates an attachment with a model

    In both these cases entries are made in the table and files are uploaded. Unfortunately, they are completely inaccessible. You can delete any entries from your attachments table that have a empty attachable_id and attachable_type columns. Don’t forget to delete the associated files!

    Additionally, since create and upload are seperate methods, it is theoretically possible to create an entry in the table and not upload the corresponding file. I’ve set SWFUpload to automatically upload (as opposed to creating a queue) so I’ve never seen it happen, but it is possible.