July 21, 2009
Older: Code Review: Weary
Newer: Getting Started With MongoMapper and Rails
Uploadify and Rails 2.3
A few weeks back we (Steve and I) added multiple asset upload to Harmony using Uploadify. If you are thinking that sounds easy, you would be sorely mistaken. Uploadify uses flash to send the files to Rails. This isn’t a big deal except that we are using cookie sessions on Harmony and flash wasn’t sending the session information with the files, so to Rails the files appeared as unauthenticated.
We found multiple articles online showing how to get this working, but none of them worked as promised. At the time Harmony was running on Rails 2.2. Knowing that rack was probably the best way to solve our issue, we updated to 2.3, which was pretty painless, and started hacking. Be sure to check out a quick screencast of the finished product at some point as well.
Add Uploadify
First, we added the uploadify files and the following js to the assets/index view. We actually set many more options, but these are the ones pertinent to this article. Script is the url to post the files to. fileDataName is the name of the file field you would like to use. scriptData is any additional data you would like to post to the url.
<%- session_key_name = ActionController::Base.session_options[:key] -%>
<script type="text/javascript">
$('#upload_files').fileUpload({
script : '/admin/assets',
fileDataName : 'asset[file]',
scriptData : {
'<%= session_key_name %>' : '<%= u cookies[session_key_name] %>',
'authenticity_token' : '<%= u form_authenticity_token if protect_against_forgery? %>'
}
});
</script>
As you can see, it adds the session key and the cookie value along with the authenticity token as data that gets sent with the file. We then use a piece of rack middleware to intercept the upload and properly set the Rails session cookie.
Add Some Middleware
We created an app/middleware directory and added it to the load path in environment.rb.
%w(observers sweepers mailers middleware).each do |dir|
config.load_paths << "#{RAILS_ROOT}/app/#{dir}"
end
Next, we dropped flash_session_cookie_middleware.rb in the app/middleware directory.
require 'rack/utils'
class FlashSessionCookieMiddleware
def initialize(app, session_key = '_session_id')
@app = app
@session_key = session_key
end
def call(env)
if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
params = ::Rack::Utils.parse_query(env['QUERY_STRING'])
unless params[@session_key].nil?
env['HTTP_COOKIE'] = "#{@session_key}=#{params[@session_key]}".freeze
end
end
@app.call(env)
end
end
And, finally, we added the following to our session_store.rb initializer.
ActionController::Dispatcher.middleware.insert_before(
ActionController::Session::CookieStore,
FlashSessionCookieMiddleware,
ActionController::Base.session_options[:key]
)
This inserts our middleware before ActionController’s CookieStore so that everything will just work as expected.
Assign the Content Type
The only other thing we needed to do was manually set the content type of the file. We were using paperclip (which is awesome) to do uploads, so something like this did the trick:
@asset.file_content_type = MIME::Types.type_for(@asset.original_filename).to_s
Be sure to add the mime type gem to your environment.rb file as well.
config.gem 'mime-types', :lib => 'mime/types'
But Why?
So why did we go through all this trouble to allow multiple uploads at once? Taking a quick look at the finished product might help. I didn’t record the entire screen in the video, as we haven’t actually released Harmony yet (ooooh secrets!), but I did capture enough that you can see the awesome uploads in action.
Hope this spares some other poor soul attempting the same thing some time.
19 Comments
Jul 22, 2009
Finally a simple, awesome solution to what is a hard problem. No more trying to crowbar upload.swf into applications.
Jul 22, 2009
Hi John. I’ve been trying to shoehorn uploadify into our app the last couple days (without modifying uploadify source). In our app we have an Asset model that uses paperclip to store asset records. Uploadify POSTs the upload to the create action in our assets controller which creates a new asset record, but additional form fields are not sent in that POST. Without JS/Flash all form attributes are of course posted. We’ve resorted to redirecting to an edit form (in JS) following the ajax uploadify POST of a single upload to complete the additional asset model attributes. (1) Would you mind sharing some of your model and controller code (here or in the uploadify forum)? The Rails/REST examples are lacking. I’m also planning a blog post and test app for the scenario I’m describing, email me if you want to collaborate.
Jul 22, 2009
@Andy – We are just doing files and not currently allowing changing titles and such. My model and controller does nothing special. Haven’t needed to do what you are doing yet. If I do I’ll be sure to post.
Jul 22, 2009
In practice I have found Uploadify difficult (impossible in its current form?) to work with in this regard. Things I’ve tried (1) auto:false uploadify option, and do a jquery
$.post
being sure to send extra form fields, (2) usescriptData
to grab form data with jquery and post it, (3) create a Asset record and have the form post to the update action by passing'_method':'put'
in scriptData and other means, but I can’t trick rails into going to the update action. As I mentioned we have a 2-step process now, which works for a single or multiple uploads. Second step for multiple uploads would be editing a batch of assets attributes (titles/descriptions for example), Flickr comes to mind as an example of this approach. If you add the ability for users to customize the names or descriptions of their uploads with Harmony, would love to see some of your implementation!Jul 25, 2009
This has been tremendously helpful. However, I seem to have a slight issue that I can’t figure out. Below I have pasted my “scriptData” portion for uploadify as it appears in the source code on the rendered page. I assume that the “u” in the code must be url-encoding these strings, so “+” becomes “%2B” or “=” becomes “%3D.”
My problem is that when uploading a file, my log shows
and the params show
Hmmm, why did the + signs turn into spaces? The = sign seemed to convert just fine. Any ideas?
Dave
Jul 25, 2009
I seem to run into the exact same problem as Dave does…. been trying all kinds of things to get the + sign encoded/decoded correctly but without luck so far…
Jul 25, 2009
@Dave and @Smeevil – That is actually Rack::Utils.parse_query. I remember hitting that at some point and having issues, but then I did something differently and the problems went away.
Can’t remember what. I can promise that my final solution is what I showed above and that it is in fact working for me. If anything hits me, I’ll let you know.
Jul 25, 2009
@John Well the encoding is solved now using :
But alas still getting the InvalidAuthenticityToken :/
The post looks good though :
Parameters: {"Filename"=>"test.png", “gallery_session”=>"66c523f937f33432a4e660906dfc0e14", “folder”=>"/smeevil/albums/7/photos/", “authenticity_token”=>"3R0ccSdAVZD3X5hWOTfzoaMvrHwRHMrS6WDy+BgWBk4=", “album_id”=>"7", “photos”=>{"content"=>#<File:/var/folders/9c/9cYOHQdXHyuCFvOglxcd7k+++TQ/-Tmp-/RackMultipart.7710.1>}, “Upload”=>"Submit Query", “userid”=>"1"}
Still investigating , if I find something I’ll let you know :)
Jul 25, 2009
Found it !
Somehow when i added the session_key_name to the script variable it worked…
==‘script’ : ‘/photos/?#{session_key_name}=#{u cookies[session_key_name]}’,
Jul 25, 2009
John, thanks for the fantastic write up. You published this tuesday. Friday uploadify 2.0.0 was released. And I needed this stuff… today. Couldn’t have been better.
Here are a few things that I needed to get mine working (please note, I’m using Uploadify 2.0.0):
I’m not sure what the session_key_name thread means, other than adding it to the script parameter might be a way to get around using the method parameter.
Anyway, my updated view:
Thanks again.
Jul 26, 2009
Excellent! Yes, I forgot to mention that I, too, was using the new Uploadify 2.0. However, I did try the 1.6.2 release with no luck either. Also, I had tried using the encodeURIComponent as well, but without the “u” method – this didn’t work. But with the “u” – it does work. Can someone explain why you need the “u” there too? From what I can tell, the u simply url encodes the string. Isn’t that the same thing that encodeURIComponent does?
Lastly, I did post to the uploadify forum about this problem. One of the developers replied saying that something has changed in how scriptData deals with variables – more info here
http://www.uploadify.com/forum/viewtopic.php?f=7&t=1446
Jul 27, 2009
Does this work with polymorphic assets?
Jul 29, 2009
Setting the method to GET for a file upload didn’t quite make sense to me. Rather, I used the Rack::Request object instead of env[‘QUERY_STRING’], which got me working. So call in the middleware now looks like:
I also had to double-wrap the scriptData that’s passed to uploadify, like so:
Hope that helps out a bit!
—riney
Jul 30, 2009
I don’t get it, isn’t Uploadify a just a version of SWFUpload with the js converted to jQuery?
Jul 31, 2009
There’s been a healthy thread in the comments going on about Middleware and Flash based uploaders since December last year on The Web Fellas blog
Looks like it has a slightly different variant on the middleware but is the version used in this swf upload and rails sample on github
Finally, Jim Neath made a few blog posts around flash uploaders that are also worth a read.
Aug 05, 2009
Spent several hours trying to get multiple asset records created with uploadify and paperclip. Always get an error in paperclip creating the second record. Put together a sample app that illustrates the problem with full details. Would anyone be able to lend a hand? Would love the help!
uploadifytest on github
Aug 06, 2009
env[‘QUERY_STRING’] is empty for me(rails 2.3.3) so I used next:
@app.call(env)
end
Aug 10, 2009
Issue was with Rails 2.3.3! Rails 2.3.2 works great. Issue filed for Paperclip.
Aug 14, 2009
Hello. I’ve been trying to follow this and other similar tutorials for Rails, paperclip, and Uploadify. It’s been tough, but I think I’m getting close. I managed to get past the authenticity woes, but now am getting other errors, such as:
NoMethodError (undefined method ‘original_filename’
or
You have a nil object…
my code is taken mostly from your example.
In my controller:
I don’t seem to have any issues with token authenticity, so I’ll leave that code out. But here’s the javascript:
I’ve tried mixing code up from different, similar tutorials, but end up making it worse, or just getting myself confused.
Rails is actually saving the image object because I have db records showing only the created_at and updated_at times. All other fields, the paperclip ones(i.e. photo_content_type, photo_file_name, etc.) are all NULL. Any help would be appreciated. Thanks.
Sorry, comments are closed for this article to ease the burden of pruning spam.