Uploadify, Paperclip, Rails 3 and Sessions
As long as HTML5 is not production-ready across all main browsers, file uploads are still a painful thing. See a demonstration about how it will be one day here.
But back to today: I’m not really a friend of flash, but in the moment it’s nearly the only possibility to get multiple-file-uploads and progress-bars. Best practice to do this is to use Uploadify, which is a jQuery plugin that allows the easy integration of a multiple (or single) file uploads on your website. It requires Flash and is compatible with any backend development language. This flash uploader sends the data to our rails controller where they are saved with the glorious help of Paperclip.
Most HOWTOs on the web are for Rails 2.x – and no one solved my former problem to get a cookie/session through Flash. So here is my working Rails 3 setup:
Patch Rack-Middleware
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/
req = Rack::Request.new(env)
env['HTTP_COOKIE'] = decode req.params["session_encoded"] unless req.params["session_encoded"].nil?
end
@app.call(env)
end
private
def decode(s)
s.split("x").map{|x| x.to_i.chr}.join
end
end
Rails.configuration.middleware.insert_before(ActionDispatch::Session::CookieStore, FlashSessionCookieMiddleware, Rails.configuration.secret_token)
Put this file into
config/initializers/flash_session_cookie_middleware.rb
to make sure it’s loaded at startup. With this code we tell Rails to use our own Rack middleware, which – if the request is coming from a Flash client – will rescue our session, otherwise we just call our Rails stack as always. Wondering what this decode method is for? It deals with the problem of Flash that it swallows some chars like ‘+’, so we have to encode our session before to make it Flash-save. As you’ll see, I do this in a very low level way with Ruby because JavaScripts encodeURI() doesn’t escape all dangerous (Flash-) chars.
The Uploadify part
Here comes a little example snippet for your view.
<script type="text/javascript">
$(function() {
// encode the session into a Flash-save format
<% arr = [] %>
<% request.env['HTTP_COOKIE'].each_char{|c| arr.push(c[0].to_s)} %>
<% session = arr.join("x") %>
$('#file_input').uploadify ({
script : '<%= band_container_items_path %>',
uploader : '/flash/uploadify.swf',
multi : true,
auto : true,
scriptData : {'session_encoded': '<%= session %>', 'foo': 'bar' },
cancelImg : '/images/cancel.png' //take care that the image is accessible
});
});
</script>
We translate the session in our own format – one that Flash 100% understands. Every char is translated to its ASCII code and connected with a “x” – so we send a string like “104x101x108x108x111x32x119x111x114x108x100″ through Flash – and that is ok.
Hope this post can get you on the right way to get your code up und running.
| Print article | This entry was posted by dirk on 22.07.2010 at 18:51, and is filed under jQuery, Plugins, Rails, Ruby. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |


about 2 years ago
Uploadify doesn’t return the fields of paperclip (photo_file_name, photo_content_type …). How do I save those params in rails3 ?
about 2 years ago
That’s right – Uploadify doesn’t return them directly. A bit of Paperclip magic happens here… Have a look at this example:
This is what arrives at our controller. Log it via
in your create-action.
"format" => "json",
"action" => "create",
"folder" => "/users/",
"Upload" => "Submit Query",
"session_encoded" => "...x53x48x102x54x57x56x99x54x...",
"model_id" => "1",
"user" => {"photo" => #<File:/tmp/RackMultipart20100920-1122-1lsvp1d-0>}}
Now create an User-Object from this params:
Logging this object via
gives you
photo_file_name: "example.jpg",
photo_content_type: "application/octet-stream",
photo_file_size: 7984,
photo_updated_at: "2010-09-20 11:31:12",
created_at: nil,
updated_at: nil>
You can see that the attributes “photo_file_name”, “photo_content_type”, “photo_file_size” and “photo_updated_at” are set magically from Paperclip. Only problem here is “photo_content_type” – we don’t want “application/octet-stream” (that’s sent by Flash). The trick here is to use the gem Mime-Types.
Require it in your Gemfile:
In your create-action you can now set “photo_content_type” manually:
@user.photo_content_type = mime_type.first.content_type.to_s if mime_type.first
Logging @user again gives you now:
photo_file_name: "example.jpg",
photo_content_type: "image/jpeg",
photo_file_size: 7984,
photo_updated_at: "2010-09-20 11:31:12",
created_at: nil,
updated_at: nil>
Hope I could help, Dirk.
about 2 years ago
Thanks Dirk. I was really helpful.
about 2 years ago
I meant to say – “It was really helpful.”
about 2 years ago
The progress bar of uploadify starts very late. The upload goes on with no progress bar at all. When the upload is finished, the progress bar appear and disappear quite fast. How do I solve this?
about 2 years ago
NOTE : The problem with progress bar that I mentioned above occurs when I upload my files to Amazon S3.
about 2 years ago
Ok, that’s why I never had this problem – didn’t use Uploadify in combination with S3 yet. Please keep us posted if you find a solution.
about 2 years ago
I am having the same issue — did you find a solution? Thanks!
about 2 years ago
Hello
Do you have an application example to see how you managed to save multiple uploads?
Thanks.
about 2 years ago
Hi!
Multiple uploads are handled like single uploads – the only difference is that with multiple uploads the create action in the controller gets called multiple times.
All you have to do is set the parameter “multi” to “true” like in my example above.
about 2 years ago
so this is all working, however i am trying to on each uploadify JS request, save the file data to a session temporarily so that i can later process them with another form. for whatever reason, i cannot save session data from my controller on the uploadify JS requests, but it does work with a normal HTML request. any thoughts?
about 2 years ago
First thoughts without knowing your code: a (cookie-) session just can store up to 4K. And did you take into account that you don’t get the pure filedata in the controller, but a reference to to temporary file?
about 2 years ago
yeah i was just going to save the temp file path, but I hadn’t even gotten around to that yet. i was just trying to save a string ‘foobar’ to a cookie/session to test it.
about 2 years ago
I got HTTP Error at after the progress bar finish. And here’s the error I got from the log:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
config/initializers/flash_session_cookie_middleware.rb:14:in `call’
Any idea what should i check? It’s like the famous issue with rails 2.3.8.
about 2 years ago
I tried disabling forgery_protection for a while. I still get the same error And logger.info @user.inspect doesn’t give me attachment_file_name, attachment_file_size and attachment_updated_at. They’re all nil. Any solution?
about 2 years ago
I’m actually wondering how to do just single file uploads and having the controller the upload is for process the file when the form is submitted. Is there any way to do this?
about 2 years ago
I had to make a small change to make this work:
request.env['HTTP_COOKIE'].each_byte{|c| arr.push(c.to_s)}
about 1 year ago
Thanks for that last comment, it is important if you use ruby 1.9.2!
about 1 year ago
In my 3.0.7 rails application, form was posting uploadify parameters directly to the params[] Hash, instead of params[:model]. Because of this, I had to make some modifications to my controller. I’m not sure why, any ideas? To get around this I was putting a few parameters in the :scriptData parameter of the uploadify method.
Also, I had uploadify posting to a .js file, but it wasn’t calling the response from the .js file to the page. Should it be? To work around this, I used the uploadify
nComplete parameter and made an ajax call to upload the recently uploaded item on the page.
Last question, can the uploadify function that overrides the submit button be used in conjunction with other form parameters easily? Or do you have to again use the :scriptData parameters?
Thanks for you help. And great post! Thanks.
about 2 years ago
Found a solution to disappearing bar with S3 Upload:
Try adding the “onComplete” option with something like this:
‘onComplete’ : function(event, queueID, fileObj, response, data) { $parent.find(‘#uploads’).append(response); }
Then, the bar stays and will show “100%”. The issue with late start is probably because every single uploads to S3 needs separate S3 connection that tikes some time. You could use a local file upload first and then install a delayed_job or something similar to send files to S3 in a background task afterwards.