简体   繁体   中英

Rails - Paper_Clip - Support for Multi File Uploads

I have paper_clip installed on my Rails 3 app, and can upload a file - wow that was fun and easy!

Challenge now is, allowing a user to upload multiple objects. Whether it be clicking select fileS and being able to select more than one. Or clicking a more button and getting another file upload button.

I can't find any tutorials or gems to support this out of the box. Shocking I know...

Any suggestions or solutions. Seems like a common need?

Thanks

Okay, this is a complex one but it is doable. Here's how I got it to work.

On the client side I used http://github.com/valums/file-uploader , a javascript library which allows multiple file uploads with progress-bar and drag-and-drop support. It's well supported, highly configurable and the basic implementation is simple:

In the view:

<div id='file-uploader'><noscript><p>Please Enable JavaScript to use the file uploader</p></noscript></div>

In the js:

var uploader = new qq.FileUploader({
   element: $('#file-uploader')[0],
   action: 'files/upload',
   onComplete: function(id, fileName, responseJSON){
     // callback
   }
});

When handed files, FileUploader posts them to the server as an XHR request where the POST body is the raw file data while the headers and filename are passed in the URL string (this is the only way to upload a file asyncronously via javascript).

This is where it gets complicated, since Paperclip has no idea what to do with these raw requests, you have to catch and convert them back to standard files (preferably before they hit your Rails app), so that Paperclip can work it's magic. This is done with some Rack Middleware which creates a new Tempfile (remember: Heroku is read only):

# Embarrassing note: This code was adapted from an example I found somewhere online
# if you recoginize any of it please let me know so I pass credit.
module Rack
  class RawFileStubber

    def initialize(app, path=/files\/upload/) # change for your route, careful.
      @app, @path = app, path
    end

    def call(env)
      if env["PATH_INFO"] =~ @path
        convert_and_pass_on(env)
      end
      @app.call(env)
    end

    def convert_and_pass_on(env)
      tempfile = env['rack.input'].to_tempfile      
      fake_file = {
        :filename => env['HTTP_X_FILE_NAME'],
        :type => content_type(env['HTTP_X_FILE_NAME']),
        :tempfile => tempfile
      }
      env['rack.request.form_input'] = env['rack.input']
      env['rack.request.form_hash'] ||= {}
      env['rack.request.query_hash'] ||= {}
      env['rack.request.form_hash']['file'] = fake_file
      env['rack.request.query_hash']['file'] = fake_file
      if query_params = env['HTTP_X_QUERY_PARAMS']
        require 'json'
        params = JSON.parse(query_params)
        env['rack.request.form_hash'].merge!(params)
        env['rack.request.query_hash'].merge!(params)
      end
    end

    def content_type(filename)
      case type = (filename.to_s.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
      when %r"jp(e|g|eg)"            then "image/jpeg"
      when %r"tiff?"                 then "image/tiff"
      when %r"png", "gif", "bmp"     then "image/#{type}"
      when "txt"                     then "text/plain"
      when %r"html?"                 then "text/html"
      when "js"                      then "application/js"
      when "csv", "xml", "css"       then "text/#{type}"
      else 'application/octet-stream'
      end
    end
  end
end

Later, in application.rb:

config.middleware.use 'Rack::RawFileStubber'

Then in the controller:

  def upload
    @foo = modelWithPaperclip.create({ :img => params[:file] })
  end

This works reliably, though it can be a slow process when uploading a lot of files simultaneously.

DISCLAIMER

This was implemented for a project with a single, known & trusted back-end user. It almost certainly has some serious performance implications for a high traffic Heroku app and I have not fire tested it for security. That said, it definitely works.

The method Ryan Bigg recommends is here:

The file-uploader recommendation by Daniel Mendel is really great. It's a seriously awesome user experience, like Gmail drag-and-drop uploads. Someone wrote a blog post about how to wire it up with a rails app using the rack-raw-upload middleware, if you're interested in an up-to-date middleware component.

There's also another plugin that's been updated more recently which may be useful

And another one (Included for completeness. I haven't investigated this one.)

These questions are highly related

I cover this in Rails 3 in Action 's Chapter 8. I don't cover uploading to S3 or resizing images however.

Recommending you buy it based solely on it fixing this one problem may sound a little biased, but I can just about guarantee you that it'll answer other questions you have down the line. It has a Behaviour Driven Development approach as one of the main themes, introducing you to Rails features during the development of an application. This shows you not only how you can build an application, but also make it maintainable .

As for the resizing of images after they've been uploaded, Paperclip's got pretty good documentation on that . I'd recommend having a read and then asking another question on SO if you don't understand any of the options / methods.

And as for S3 uploading, you can do this:

has_attached_file :photo, :styles => { ... }, :storage => :s3

You'd need to configure Paperclip::Storage::S3 with your S3 details to set it up, and again Paperclip's got some pretty awesome documentation for this.

Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM