いい加減ドラッグ&ドロップでファイルをアップしたい!
あまのです。
社内プロジェクトで久々にRubyとRailsをさわりました。
やっぱりRubyは書きやすくていいですね。
さて今回はドラッグ&ドロップで複数ファイルのアップロードです。
前々から、「そろそろブラウザでもドラッグ&ドロップでファイルアップロードしたい」と思ってたので、今回試しに作ってみました。
参考にしたサイト
篳篥日記
http://d.hatena.ne.jp/hichiriki/20101016
デモ
今回作るサンプルのデモを最初にお見せします。
chromeやSafari, Firefox3.6でUpload a fileに画像ファイルをドラッグ&ドロップしてみてください。
目標
- 最近のブラウザではドラッグ&ドロップでアップロード
- 対応していないブラウザは普通にファイルアップロード
- 複数ファイルに、もちろん対応
- Herokuの無料プランでも使えるようにファイルはサーバ内に置かず、AmazonS3へ保存
1,2,3はfileuploader.jsで、4はpaperclipで。
どんどん便利になっていくなぁと思いました。
環境
ruby 1.8.7
Rails 3.0.1
paperclip 2.3.5
fileuploader
事前準備
Ruby + Rails + Paperclipの環境をまず整えます。
また今回のメインである下記のライブラリをダウンロードします。
valums / file-uploader
http://github.com/valums/file-uploader
ダウンロード後、JSとCSS、ローディングの画像を配置します。
$ mv fileuploader.css public/stylesheets/
$ mv loading.gif public/images/
loading.gifが正しく呼び出されるようにfileuploader.cssを少し修正しました。
↓
Amazon S3の設置と設定
今回はS3に設置しますので、Amazon S3の契約をして、アクセスキーとシークレットキーの取得します。
取得したアクセスキーとシークレットキーはHerokuのConfig Varsに設定します。
モデルの作成
ほぼpaperclipのサンプルのままですが、Amazon S3など環境に合うように設定しました。
app/models/users.rb
# original_filename: config/initializers/paperclip.rb
has_attached_file :avatar,
:storage => :s3,
:s3_credentials => {
:access_key_id => ENV['S3_KEY'],
:secret_access_key => ENV['S3_SECRET']
},
:bucket => ENV['S3_BUCKET'],
:styles => { :medium => "300x300>", :thumb => "100x100>" },
:path => "/dev/ajaxupload-demo/:id/:style_:original_filename",
:url => "/dev/ajaxupload-demo/:id/:style_:original_filename"
end
def self.up
add_column :users, :avatar_file_name, :string
add_column :users, :avatar_content_type, :string
add_column :users, :avatar_file_size, :integer
add_column :users, :avatar_updated_at, :datetime
end
def self.down
remove_column :users, :avatar_file_name
remove_column :users, :avatar_content_type
remove_column :users, :avatar_file_size
remove_column :users, :avatar_updated_at
end
end
ヘルパーの作成
ヘルパーメソッドを定義します。
注意点として、authenticity_tokenを設定する必要があります。
app/helpers/upload_helper.rb
def ajax_uploader_script(id, action, options={})
raw script = <<-EOS
<script type="text/javascript">
function createUploader(){
var uploader = new qq.FileUploader({
element: document.getElementById('#{ id }'),
action: '#{ action }',
debug: true,
params: {
authenticity_token: '#{ form_authenticity_token }'
},
onComplete: function(id, fileName, responseJSON) {
location.reload();
}
});
}
window.onload = createUploader;
</script>
EOS
end
end
ビューの作成
app/views/entries/new.html.erb
<%= javascript_include_tag 'fileuploader' %>
コントローラーの作成
今回の肝であるコントローラーの作成です。
最初ドラッグ&ドロップした時に Rails 側でうまく受け取ることができず、結構はまりました。
fileuploader.jsはドラッグ&ドロップでファイルをアップロードする時、XMLHttpRequestでファイルを送るのですが、Content-typeはapplication/octet-streamで送られます。multipart/form-dataで送られていないことに注意です。私はここではまりました…
またファイル一つ一つ、それぞれXMLHttpRequestで送られてきます。
Rails側では、XMLHttpRequestが使われた時に、リクエストデータをそのままファイルデータとして保存します。XMLHttpRequestで送られなかった場合は、通常のmultipart/form-dataで送られるため、普段のファイルアップロードと同じようにparams[:qqfile]をFileオブジェクトとして扱います。
Tempfileを使っているのは、Amazon S3に配置するために一時ファイルを作成して、paperclipに渡します。これでHerokuでも使えます。
fileuploader.js 側でアップロードが成功したかどうかを次の JSON を受け取れるかで判定しているため、受け取りに成功したら返すようにします。
失敗した場合は、下記のように返します。
app/controllers/entries_controller.rb
def new
@entry = Entry.new
@users = User.limit(5).order('id DESC')
end
def ajax_upload
begin
_store_upload(params[:qqfile])
rescue
render :json => { :error => $!.message.to_s }
else
render :json => { :success => true }
end
end
private
def _store_upload(file)
if request.xhr?
if request.body.length == request.headers['CONTENT_LENGTH'].to_i
file_data = request.body.read
file_name = file
end
else
if file.instance_of?(File)
file_data = file.read
file_name = file.original_filename
end
end
raise "upload failure" unless file_data
tf = Tempfile.new(file_name)
tf << file_data
@user = User.new
@user.avatar = tf
@user.avatar_file_name = file_name
@user.save
tf.close!
end
end
もう一つ注意する点がありました。
Tempfile を使っているため、ファイル名が「元のファイル名pid.n」みたいになります。
それを paperclip に渡しているため、paperclip がそのファイル名で保存してしまいます。
そのため、事前に file_name という変数にファイル名を入れて、avatar_file_nameに設定します。
これで大丈夫かというと、実はダメで、ここでまたはまりました。
users.rb で上記のようにoriginal_filename と書いてありますが、これは paperclip で提供されるシンボルではなく、config/initializers/paperclip.rbを作成することで、original_filenameが呼ばれたら、avatar_file_name を返すようにしています。
config/initializers/paperclip.rb
attachment.instance.avatar_file_name
end
これで完了です。
サンプルコード
githubにサンプルとしてファイルを置いてあります。
Herokuで動作するようにしてあります。
http://github.com/amano/ajaxupload-demo
取得後、次の作業を行ってください。
- HerokuのConfig VarsにS3のアクセスキーなどを設定
- app/models/users.rbをS3に合うように修正
Herokuに設置して、http://{appname}.heroku.comにアクセスすれば見えると思います。
Herokuに関しては以下のサイトがわかりやすかったので参考にしてみてください。
Ruby版PaaSの”Heroku”で無料Railsホスティング環境を手に入れよう
http://kuranuki.sonicgarden.jp/2009/05/rubypaasherokurails.html
fileuploader.jsは便利そうなので、良い使い方があればぜひ教えてください。
関連記事
このエントリーに対するコメント
-
- @blog.justoneplanet.info2010/11/02, 11:38 AM
もっとFile APIを使ってXMLHttpRequestと組み合わせてみる
XMLHttpRequest Level2ではバイナリデータもアップロードできるようになった!ヽ(=´▽`=)ノ ■ソース 基本的には前回と同じコードを利用している。 <p ondragstart="dragstart(event)" ondragenter=…
- トラックバック