Rails 3 получает исходные данные и записывает их в файл tmp

Я работаю над реализацией Ajax-Upload для загрузки фотографий в моем приложении Rails 3. В документации указано:

  • Для IE6-8, Opera, более старых версий других браузеров вы получаете файл как вы обычно делают с обычной формой загрузки.

  • Для браузеров, которые загружают файл с индикатором выполнения, вам необходимо получить исходных данных и записать их в файл.

Итак, как я могу получить исходные данные в моем контроллере и записать его в файл tmp, чтобы мой контроллер мог его обработать? (В моем случае контроллер выполняет некоторые манипуляции с изображениями и сохраняет их на S3.)

Дополнительная информация:

Поскольку я настроен прямо сейчас, сообщение передает эти параметры:

Parameters:
{"authenticity_token"=>"...", "qqfile"=>"IMG_0064.jpg"}

... и действие CREATE выглядит так:

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

... но я получаю эту ошибку:

ActiveRecord::RecordInvalid (Validation failed: File file name must be set.):
  app/controllers/attachments_controller.rb:7:in `create'

Ответы

Ответ 1

Это потому, что params [: qqfile] не является объектом UploadedFile, а строкой, содержащей имя файла. Содержимое файла сохраняется в теле запроса (доступно с помощью request.body.read). Конечно, вы не можете забыть о обратной совместимости, поэтому вам все равно придется поддерживать UploadedFile.

Итак, прежде чем вы сможете обрабатывать файл в унифицированном виде, вам нужно поймать оба случая:

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

Ответ 2

попробуйте, добавьте 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

в файле assets_controller:

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}
});

Ответ 3

Другое решение:

gem 'rack-raw-upload', :git => 'git://github.com/tb/rack-raw-upload.git'

и в config.ru:

 require 'rack/raw_upload'
 use Rack::RawUpload

и использовать параметры [: файл] в контроллере.

Ответ 4

Вместо создания некоторого временного файла, вы можете использовать StringIO. См. Мой ответ о CarrierWave, здесь: fooobar.com/info/395256/...