SWFUpload on Rails
Note: This has been tested with SWFUpload v2.2.0.1 and Ruby on Rails v2.3.4
Note: This is used together with restful-authentication.
SWFUpload is a Flash uploader, that enhances the experience of uploading. Some examples include the ability to select multiple files at once, and queue them up for upload. For more features, visit the SWFUpload website. This is a great tool if your website requires you to upload multiple files.
But there are documented issues SWFUpload and using it with Ruby on Rails.
By default, when using SWFUpload with Ruby on Rails, SWFUpload works great. Everything works as documented, well at least as far as I can tell. By default I mean:
- You are not using authentication (for example login)
- If you are using authentication, but uploading to a page that doesn't require login
Problems with SWFUpload and Ruby on Rails
SWFUpload breaks on Ruby on Rails when you using authentication, and when you have to upload to a page that requires login. The result of doing so is that Rails will think your user is not currently logged in, and will deny the upload request possibly by redirecting the request to the login page. This bug is caused Flash not being able to use the same session as your web browser.
Now when you Google, you will notice multiple methods of solving this problem. Notably a fix to rack and the middleware. Now I don't want to hack my rack, or middleware. Plus I found all the documentation rather confusing.
swfupload.cookies.js
Instead what I wanted to focus on, was in the tarball for SWFUpload. Inside I noticed a file called swfupload.cookies.js. The file describes itself as:
Cookie Plug-in
This plug in automatically gets all the cookies for this site and adds them to the post_params.
Cookies are loaded only on initialization. The refreshCookies function can be called to update the post_params.
The cookies will override any other post params with the same name.
When you include swfupload.cookies.js into your application, your params will include the keys and values from your cookies. For example, if you had a cookie called "auth_token", with a value of "d2f423cd5559645eb8e75230b9ce2648d59a7ad8", it would appear in your params as params[:auth_token] = 'd2f423cd5559645eb8e75230b9ce2648d59a7ad8'.
This was the perfect tool I needed.
Getting SWFUpload to work with Rails
Note: Once again, this tutorial is for use with restful-authentication. We will assume that your User model is called "User", and your Session controller is called "SessionsController". We will also assume that the swfupload files are in /public/javascripts
Using swfupload.cookies.js
In your Rails app, include swfupload.cookies.js. I decided to put it after my swfupload.js.
<%= javascript_include_tag 'swfupload.js' %> <%= javascript_include_tag 'swfupload.cookies.js' %>
Modify Your Sessions Controller
If you open up your Sessions controller ( /app/controllers/sessions_controller
) you will see a create method. This is where your User is authenticated, and their session established.
def create
logout_keeping_session!
user = User.authenticate(params[:login], params[:password])
if user
# Protects against session fixation attacks, causes request forgery
# protection if user resubmits an earlier form using back
# button. Uncomment if you understand the tradeoffs.
# reset_session
self.current_user = user
new_cookie_flag = (params[:remember_me] == '1')
handle_remember_cookie! new_cookie_flag
flash[:notice] = 'Logged in successfully'
else
note_failed_signin
@login = params[:login]
@remember_me = params[:remember_me]
end
redirect_back_or_default('/')
end
Now we need to change lines 10-11.
From:
new_cookie_flag = (params[:remember_me] == '1')
handle_remember_cookie! new_cookie_flag
To:
handle_remember_cookie! true
Yes, we are going to make the User remember the cookie every time he logs in.
Modifying lib/authenticated_system.rb
Now for all this to work we need to edit lib/authenticated_system.rb.
Look for the current_user.
def current_user
@current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie) unless @current_user == false
end
Now we are going to add a new method call to that, and change the method current_user to:
def current_user
@current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || login_from_params) unless @current_user == false
end
Now we create a new method called login_from_params.
# Created specifically for swfupload, as the cookie can't be passed via flash, and is instead passed via a post param
def login_from_params
binder = params[:auth_token] && User.find_by_remember_token(params[:auth_token])
if user && user.remember_token?
self.current_user = binder
end
end
This is where the magic happens. The user will be logged in based on their :auth_token.
Testing
Make sure you log out the user, and login again as we have to create the cookie.
Conclusion
There you have it! A working SWFUpload with Rails. Enjoy.
