Rails アプリでファイルアップロードに Dropzone.js を使う


概要

Dropzone.js という JavaScript ライブラリがクールだったので、これを Rails アプリケーションで利用する方法をご紹介します。

Dropzone.js について

ドラッグ & ドロップによるファイルアップロード機能を提供する JavaScript ライブラリです。通信は Ajax で行われます。

この形式は PC 向けの Web アプリケーションでは結構見かけると思います。この Qiita でも使われていますね。

Dropzone.js 見た目は以下のとおりです。なお、デフォルトでは外枠は実線ですが、公式サイトのデモに合わせて CSS で破線にしています。

ここにファイルをドラッグ & ドロップするとこのようになります。プレビューとして、画像ファイルはサムネイルが表示され、ドキュメントファイルはファイルサイズやファイル名などの情報が表示されます。

また、画像ファイルにマウスカーソルをホバーすると、ドキュメントファイルと同様にファイル情報が表示されます。

おしゃれですね

ちなみに、ファイルは複数同時にドラッグ & ドロップすることが可能です。

導入

では実際に Rails アプリケーションに導入したいと思います。

dropzonejs-rails という Gem が公開されているので、まずはそれをインストールします。

Gemfile
# 2016/03/09 時点での最新バージョンは 0.7.3 です。
gem 'dropzonejs-rails', '~> 0.7.3'
$ bundle install

そして application.jsapplication.css に以下を追記します。

app/assets/javascripts/application.js
//= require dropzone
app/assets/stylesheets/application.css
*= require dropzone/basic
*= require dropzone/dropzone

次に View を実装します。まず例として、僕の Rails アプリケーションでは、ファイルアップロードのために以下のような Model と Controller を実装済みと仮定します。特定の書類 (Document) に対して添付ファイル (Attachment) を作成するイメージです。

app/models/document.rb
class Document < ActiveRecord::Base
  has_many :attachments, dependent: :destroy
end
app/models/attachment.rb
class Attachment < ActiveRecord::Base
  belongs_to :document

  # この例ではサーバサイドでのファイルアップロードに Paperclip という Gem を利用している想定です。
  # ただこの記事はサーバサイドでのファイルアップロード方法には関係しないので、
  # お好きな Gem をご利用ください。
  has_attached_file :file
end
app/controllers/attachments_controller.rb
class AttachmentsController < ApplicationController
  def create
    @attachment = Attachment.create!(create_params)
  end

  private

  def create_params
    params.require(:attachment).permit(:document_id, :file)
  end
end

Dropzone.js 用の View は form_tag を使って以下のように実装します。

app/views/documents/edit.html.erb
<%= form_tag(attachments_path, class: 'dropzone', id: 'upload-dropzone') do %>
  <div class="fallback">
    <%= file_field_tag('attachment[file]') %>
  </div>
<% end %>

なお、file_field_tag の第 1 引数、つまり input 要素の name 属性は JavaScript 側で明示する必要があります。さもないと attachment[file]file になってしまいます。そのため、実際は file_field_tag の引数は任意の値で構いません。

次にクライアントサイドを実装します。

Dropzone.autoDiscover = false

new Dropzone '#upload-dropzone',
  uploadMultiple: false
  paramName: 'attachment[file]'
  params:
    'attachment[document_id]': 123
  init: ->
    @on 'success', (file, json) ->
      # アップロード成功時の処理をここに実装します。
  dictDefaultMessage: '''
    <i class="fa fa-file-o fa-2x"></i><br>
    <br>
    ファイルをここにドロップするか<br>
    ここをクリックして下さい
  '''

パラメータを { attachment: { file: ... } という具合にネストさせたいので paramName を指定します。また、他にもパラメータを渡す必要がある場合は params オプションで指定します。ちなみに、この params オプションは 公式サイトの説明 に載っておらず、GitHub の Issue で知りました。

最後に CSS で見た目を調整します。最初に述べたとおり、僕はボーダーを破線にしたり角を丸くしたりしています。また、Dropzone の中に表示される文言の表示も調整しています。

@import 'compass/css3/border-radius';

.dropzone {
  border: 2px dashed #b4bcc2;
  @include border-radius(4px);

  .dz-message {
    font-size: 16px;
    text-align: center;
    margin: 0
  }
}

以上で実装は完了です。

おまけ

プレビューを非表示にする

プレビューを表示したくない場合はこのようにします。

Dropzone.autoDiscover = false

new Dropzone '#upload-dropzone',
  uploadMultiple: false
  paramName: 'attachment[file]'
  params:
    'attachment[document_id]': 123
  init: ->
    @on 'success', (file, json) ->
      # アップロード成功時の処理をここに実装します。
  dictDefaultMessage: '''
    <i class="fa fa-file-o fa-2x"></i><br>
    <br>
    ファイルをここにドロップするか<br>
    ここをクリックして下さい
  '''
  previewTemplate: '<div style="display:none"></div>' # このオプションを追加します。
// アップロード後も dictDefaultMessage の文言を表示したままにします。
.dropzone.dz-started .dz-message {
  display: block;
}

参考