この問題は何時間も私を悩ませてきました、そして私はそれに対する解決策を見つけることができないようです。
ユーザーがcarrierwave_direct、fog、およびcarrierwave(carrierwave_directの依存関係)を使用してAmazonS3アカウントにファイルをアップロードできるRails3.2アプリがあります。carrierwave_directを使用すると、ユーザーはファイルをAmazon S3に直接POSTすることで、サーバーへのファイルのアップロードをスキップできます(大きなファイルの場合、サーバーの処理とHerokuのようなタイムアウトを節約できます)。
1つのファイルを選択し、それをAmazonにアップロードし、Amazonに提供するURLにredirect_toが必要な場合は、正常に機能します。これは、フォームをAmazon S3にPOSTすることで実行され、Amazonは指定されたURL(フォームでこのURLを指定)に応答し、URLにいくつかのパラメーターを設定します。これらのパラメーターは、モデル内のAmazon上のファイルへのポインターとして保存されます。
したがって、ライフサイクルは次のとおりです。1つのファイルを選択し、AmazonにPOSTすると、Amazonは別のページに移動するURLで応答し、Amazonファイルへのポインターを使用してレコードを保存できます。
私が理解しようとしているのは、複数のファイルを選択してアップロードし、アップロードの進行状況を更新するにはどうすればよいですか?私はこれを純粋なJavaScriptで(最新のブラウザーで提供されているファイルAPIを使用して)実行しようとしているので、サードパーティのツールは必要ありません。また、これを深く学ぶために、私はプラグインを避け、自分でコードを書こうとしています。
私が取得しようとしている機能は次のとおりです。
- ユーザーにはファイルフィールド(またはドラッグアンドドロップ)のあるフォームが表示されます
- ユーザーが複数のファイルを選択します(ファイルフィールドをクリックするか、ドラッグアンドドロップします)
- Javascript(サーバーはまだありません)を使用して、アップロードする選択したファイルのキューを作成します(ブラウザーのファイルAPIを使用して、ファイル名とサイズのみ)
- 次に、ユーザーは[アップロードの開始]ボタンをクリックします
- キュー内の各ファイルを繰り返し処理し、ファイルをAmazonS3にPOSTします。Amazonは個々のPOSTにURLで応答し、そのURLは標準のリクエストとしてではなく、Javascriptを介して処理する必要があります。Amazonが提供するURLは、Amazonファイルへのポインターを格納するレコードを作成します。レコードが作成されると、コードは完了するまでキュー内の次のファイルに移動します。
この時点で、個別のプログレスバーがなくても実行できました。ページを更新せずに複数のファイルをAmazonS3にPOSTするだけでよかったです。
私はどの宝石にも偏っていません。本当に特定の方法でやりたいのなら、やりたいことを一から書かなければならないのではないかと実際に恐れています。目標は、AJAXを介してAmazonS3アカウントに複数のファイルをアップロードすることです。問題への取り組み方の一般的な概念でさえ、私は恍惚となるでしょう。私はこれをグーグルで検索するのに何時間も費やしました、そして私はちょうど私が望むことをする解決策を見つけられませんでした。どんな助けでも大歓迎です。
編集2014-03-02
Rajは、複数のアップロードをどのように実装したかを尋ねました。長い間、私がしたことの背後にあるすべての「理由」を思い出せません(おそらく、初めてだったので、とにかく悪いコードです)が、これが私が行っていたことです。
私がアップロードしたモデルは、AmazonS3に保存されている関連画像を持つお客様の声でした。これにより、ユーザーは複数の画像を選択して(実際には、画像に変換したPDFファイルだと思います)、画面にドラッグアンドドロップすることができました。アップロード中に、所要時間に関するフィードバックをユーザーに提供するモーダルを表示しました。
私はこれの多くで私が何をしていたかを覚えているふりをしませんが、それが助けになるならそれを自由に使ってください。
# Gemfile
# For client-side multiple uploads
gem "jquery-fileupload-rails"
# For file uploads and Amazon S3 storage
gem "rmagick"
gem "carrierwave"
gem "fog"
ビューは次のとおりです。
# app/views/testimonials/new.html.erb
<div id="main" class="padded">
<div class="center">
<div id="dropzone">
Click or Drop Files here to Upload
</div>
<%= form_for @testimonial do |f| %>
<div class="field">
<%= file_field_tag :image, multiple: true, name: "testimonial[image]", id: "testimonial_image" %>
</div>
<% end %>
</div>
</div>
<div id="mask"></div>
<div id="modal">
<h1>
Uploading <span id="global-upload-count">0</span> Files...
</h1>
<div id="global-progress">
<div id="global-progress-bar" style="width: 0%">
<div id="global-progress-percentage">0%</div>
</div>
</div>
<div id="global-processing">
<span class="spinner"></span> Processing...<span id="global-processing-count">0</span> sec
</div>
</div>
<script id="template-upload" type="text/x-tmpl">
<div class="upload">
{%=o.name%} ({%=o.readable_size%})
<div class="float-right percentage"></div>
<div class="progress"><div class="bar" style="width: 0%"></div></div>
</div>
</script>
そしてJS:
number_to_human_size = (bytes) ->
sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]
dropzone_hover = (e) ->
e.preventDefault()
$(this).addClass("dropzone-hover")
dropzone_leave = (e) ->
e.preventDefault()
$(this).removeClass("dropzone-hover")
jQuery ->
global_count = 0
seconds_to_process = 0
processing_factor = 5 # seconds to convert/process each uploaded file
$("#testimonial_image").hide()
dropzone = $("#dropzone")
dropzone.bind "click", (e) ->
$("#testimonial_image").click()
dropzone.bind("dragover", dropzone_hover)
dropzone.bind("dragleave", dropzone_leave)
dropzone.bind("drop", dropzone_leave)
$("#new_testimonial").data("global-count", "0")
$("#new_testimonial").fileupload
dropZone: $("#dropzone")
maxFileSize: 5000000 # 5 MB
dataType: "script"
add: (e, data) ->
file = data.files[0]
file.readable_size = number_to_human_size(file.size)
data.context = $(tmpl("template-upload", file).trim())
$("#new_testimonial").append(data.context)
data.submit()
global_count += 1
progress: (e, data) ->
if data.context
progress = parseInt(data.loaded / data.total * 100, 10)
data.context.find(".bar").css("width", progress + "%")
data.context.find(".percentage").text(progress + "%")
submit: (e, data) ->
$("#mask").show()
$("#modal").center().show()
progressall: (e, data) ->
$("#global-upload-count").text(global_count)
global_progress = parseInt(data.loaded / data.total * 100, 10)
$("#global-progress-bar").css("width", global_progress + "%")
$("#global-progress-percentage").text(global_progress + "%")
if global_progress >= 100
seconds_to_process = global_count * processing_factor
$("#global-processing-count").text(seconds_to_process)
$("#global-processing").show()
timer = setInterval(->
seconds_to_process = seconds_to_process - 1
$("#global-processing-count").text(seconds_to_process)
if seconds_to_process == 0
clearInterval(timer)
global_count = 0
seconds_to_process = 0
$("#modal, #mask").hide(0)
, 1000)
お客様の声モデル:
class Testimonial < ActiveRecord::Base
mount_uploader :image, ImageUploader
def display_name
if name.blank?
return "Testimonial #{self.id}"
else
return name
end
end
end