[Rails3] ドラッグ&ドロップでファイルアップロード このエントリをはてなブックマークに登録

2010年11月02日

amachinamachin / , ,

いい加減ドラッグ&ドロップでファイルをアップしたい!

あまのです。
社内プロジェクトで久々にRubyとRailsをさわりました。
やっぱりRubyは書きやすくていいですね。

さて今回はドラッグ&ドロップで複数ファイルのアップロードです。
前々から、「そろそろブラウザでもドラッグ&ドロップでファイルアップロードしたい」と思ってたので、今回試しに作ってみました。

参考にしたサイト

篳篥日記
http://d.hatena.ne.jp/hichiriki/20101016

デモ

今回作るサンプルのデモを最初にお見せします。
chromeやSafari, Firefox3.6でUpload a fileに画像ファイルをドラッグ&ドロップしてみてください。

目標

  1. 最近のブラウザではドラッグ&ドロップでアップロード
  2. 対応していないブラウザは普通にファイルアップロード
  3. 複数ファイルに、もちろん対応
  4. 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.js public/javascripts/
$ mv fileuploader.css public/stylesheets/
$ mv loading.gif public/images/

loading.gifが正しく呼び出されるようにfileuploader.cssを少し修正しました。

.qq-upload-spinner {display:inline-block; background: url("loading.gif"); width:15px; height:15px; vertical-align:text-bottom;}

.qq-upload-spinner {display:inline-block; background: url("/images/loading.gif"); width:15px; height:15px; vertical-align:text-bottom;}


Amazon S3の設置と設定

今回はS3に設置しますので、Amazon S3の契約をして、アクセスキーシークレットキーの取得します。
取得したアクセスキーとシークレットキーはHerokuのConfig Varsに設定します。

$ heroku config:add S3_BUCKET=mybucket S3_KEY=123456... S3_SECRET=abcdef...


モデルの作成

ほぼpaperclipのサンプルのままですが、Amazon S3など環境に合うように設定しました。

app/models/users.rb

class User < ActiveRecord::Base
  # 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
class AddAvatarColumsToUser < ActiveRecord::Migration
  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

module UploaderHelper
  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

<%= stylesheet_link_tag 'fileuploader' %>
<%= javascript_include_tag 'fileuploader' %>
<%= ajax_uploader_script("file-uploader", ajax_upload_new_entry_path) %>
<div id="file-uploader">
  <noscript>Please enable JavaScript</noscript>
</div>


コントローラーの作成

今回の肝であるコントローラーの作成です。

最初ドラッグ&ドロップした時に 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 を受け取れるかで判定しているため、受け取りに成功したら返すようにします。

{ success: true }

失敗した場合は、下記のように返します。

{ error: 'エラーメッセージ' }

app/controllers/entries_controller.rb

class EntriesController < ApplicationController
  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に設定します。
これで大丈夫かというと、実はダメで、ここでまたはまりました。

:path => "/dev/ajaxupload-demo/:id/:style_:original_filename",

users.rb で上記のようにoriginal_filename と書いてありますが、これは paperclip で提供されるシンボルではなく、config/initializers/paperclip.rbを作成することで、original_filenameが呼ばれたら、avatar_file_name を返すようにしています。

config/initializers/paperclip.rb

Paperclip.interpolates :original_filename do |attachment, style|
  attachment.instance.avatar_file_name
end

これで完了です。


サンプルコード

githubにサンプルとしてファイルを置いてあります。
Herokuで動作するようにしてあります。
http://github.com/amano/ajaxupload-demo

取得後、次の作業を行ってください。

  1. HerokuのConfig VarsにS3のアクセスキーなどを設定
  2. 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は便利そうなので、良い使い方があればぜひ教えてください。


クレイについてもっと知りたい方は…

  1. クレイの3つの強みを見てみる。
  2. WEBシステムのことなら何でもご相談ください。

「いいね!」で応援よろしくお願いします!

このエントリーに対するコメント

  1. とても参考になりました。

    Rails で画像等の管理を行いたいと思い、こちらの記事にたどり着きました。 git も理解の助けになりました。

    ありがとうございます。

    ごんし

    2011年07月02日, 4:42 AM

  2. ありがとうございます。
    何か不明な点や不具合などありましたら、ご連絡ください。

    amachin

    2011年07月04日, 12:26 PM

  3. ちょうど探していたモノでした。難しそうですが、使えるように頑張りたいです。

    K.naito

    2011年09月13日, 7:58 PM

  4. I must say I am very impressed with how you and your site effectively, the messages are very informative. Really captured the attention of many apparently to go!

    Online Directory listing

    2011年11月04日, 8:07 PM

  5. これを使ってIEでajaxのアップロードを行いたいのですが、ドラッグアンドドロップの機能を付けなければIEで使用可能でしょうか?
    どのように書けばいいか教えていただけないでしょうか?

    開発環境
    Rails 3.0.7
    ruby 1.9.2p290

    ka

    2011年12月07日, 4:57 PM

  6. すみません!
    私もIEでの利用はよく調査しておりません。

    amachin

    2011年12月07日, 5:04 PM

  7. たびたびすみません。
    ajaxを使ってファイルのアップロードしたいのですが、上のコード参考にrails3で実装させていただきました。
    ドラッグアンドドロップの機能はなしでいいのですが、IEで動かしたい場合はどのように書けばいいのか教えていただけないでしょうか?
    お手数ではございますがよろしくお願いします。

    ka

    2011年12月07日, 5:14 PM

  8. ソフトウェアは、ドラッグアンドドロップのようにユーザーフレンドリーな相間を提供していますがはるかに人気の高い実装が困難であるソフトウェアよりもなります

    technical writers

    2012年05月11日, 3:05 PM

  9. ですが、IEで動かしたい場合はどのように書けばいいのか教えていただけないでしょうか?

    online casino

    2013年01月03日, 9:11 PM

  10. ちらの記事にたどり着きました。 git も理解の助けになりました。

    rushessays

    2013年01月18日, 7:25 PM

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


トラックバック
  1. @blog.justoneplanet.info2010/11/02, 11:38 AM

    もっとFile APIを使ってXMLHttpRequestと組み合わせてみる

    XMLHttpRequest Level2ではバイナリデータもアップロードできるようになった!ヽ(=´▽`=)ノ ■ソース 基本的には前回と同じコードを利用している。 <p ondragstart="dragstart(event)" ondragenter=…

  2. [Rails3] ドラッグ&ドロップでファイルアップロード | KRAY Inc | Ruby On Rails ニュース2010/11/03, 3:01 PM

    […] Ruby On Railsに関する、はてなブックマーク新着情報です。 [Rails3] ドラッグ&ドロップでファイルアップロード | KRAY Inc […]

  3. fleximage と fileuploader.js | 大縄亮のブログ2011/05/26, 12:35 AM

    […] http://kray.jp/blog/rails3-fileupload/ […]

we use!!Ruby on RailsAmazon Web Services

このページの先頭へ