Rails 3 get raw post data and write it to tmp file
I'm working on implementing Ajax-Upload for uploading photos in my Rails 3 app. The documentation says:
For IE6-8, Opera, older versions of other browsers you get the file as you normally do with regular form-base uploads.
For browsers which upload file with progress bar, you will need to get the raw post data and write it to the file.
So, how can I receive the raw post data in my controller and write it to a tmp file so my controller can then process it? (In my case the controller is doing some image manipulation and saving to S3.)
Some additional info:
As I'm configured right now the post is passing these parameters:
Parameters:
{"authenticity_token"=>"...", "qqfile"=>"IMG_0064.jpg"}
... and the CREATE action looks like this:
def create
@attachment = Attachment.new
@attachment.user = current_user
开发者_开发问答 @attachment.file = params[:qqfile]
if @attachment.save!
respond_to do |format|
format.js { render :text => '{"success":true}' }
end
end
end
... but I get this error:
ActiveRecord::RecordInvalid (Validation failed: File file name must be set.):
app/controllers/attachments_controller.rb:7:in `create'
That's because params[:qqfile] isn't a UploadedFile object but a String containing the file name. The content of the file is stored in the body of the request (accessible by using request.body.read). Ofcourse, you can't forget backward compatibility so you still have to support UploadedFile.
So before you can process the file in a uniform way you have to catch both cases:
def create
ajax_upload = params[:qqfile].is_a?(String)
filename = ajax_upload ? params[:qqfile] : params[:qqfile].original_filename
extension = filename.split('.').last
# Creating a temp file
tmp_file = "#{Rails.root}/tmp/uploaded.#{extension}"
id = 0
while File.exists?(tmp_file) do
tmp_file = "#{Rails.root}/tmp/uploaded-#{id}.#{extension}"
id += 1
end
# Save to temp file
File.open(tmp_file, 'wb') do |f|
if ajax_upload
f.write request.body.read
else
f.write params[:qqfile].read
end
end
# Now you can do your own stuff
end
try it, add lib/qq_file.rb:
# encoding: utf-8
require 'digest/sha1'
require 'mime/types'
# Usage (paperclip example)
# @asset.data = QqFile.new(params[:qqfile], request)
class QqFile < ::Tempfile
def initialize(filename, request, tmpdir = Dir::tmpdir)
@original_filename = filename
@request = request
super Digest::SHA1.hexdigest(filename), tmpdir
fetch
end
def self.parse(*args)
return args.first unless args.first.is_a?(String)
new(*args)
end
def fetch
self.write @request.raw_post
self.rewind
self
end
def original_filename
@original_filename
end
def content_type
types = MIME::Types.type_for(@request.content_type)
types.empty? ? @request.content_type : types.first.to_s
end
end
in assets_controller type this:
def create
@asset ||= Asset.new(params[:asset])
@asset.assetable_type = params[:assetable_type]
@asset.assetable_id = params[:assetable_id] || 0
@asset.guid = params[:guid]
@asset.data = QqFile.parse(params[:qqfile], request)
@asset.user_id = 0
@success = @asset.save
respond_with(@asset) do |format|
format.html { render :text => "{'success':#{@success}}" }
format.xml { render :xml => @asset.to_xml }
format.js { render :text => "{'success':#{@success}}"}
format.json { render :json => {:success => @success} }
end
end
javascript:
var photo_uploader = new qq.FileUploader({
element: document.getElementById('photo-button'),
multiple: true,
action: '/assets',
allowedExtensions: ['png', 'gif', 'jpg', 'jpeg'],
sizeLimit: 2097152,
params: {guid: $('#idea_guid').val(), assetable_type: 'Idea', klass: 'Picture', collection: true}
});
Another solution is:
gem 'rack-raw-upload', :git => 'git://github.com/tb/rack-raw-upload.git'
and in config.ru:
require 'rack/raw_upload'
use Rack::RawUpload
and use params[:file] in controller.
Rather than creating some clutter temp file, you can use StringIO. See my answer about CarrierWave, here: https://stackoverflow.com/a/8812976/478354
精彩评论