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

# <i class="fa fa-fw fa-cubes"></i> 概要

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

# <i class="fa fa-fw fa-files-o"></i> Dropzone.js について

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

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

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

![スクリーンショット 2016-03-09 10.46.23.png](https://qiita-image-store.s3.amazonaws.com/0/6697/977c37df-d4eb-8977-a9ea-c87a39277f18.png)

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

![スクリーンショット 2016-03-09 09.09.55.png](https://qiita-image-store.s3.amazonaws.com/0/6697/61a61d9e-0cbf-e943-0314-f96f3bd57085.png)

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

![スクリーンショット 2016-03-09 09.10.13.png](https://qiita-image-store.s3.amazonaws.com/0/6697/7b4e4432-689a-d1ab-a079-747a416e4b09.png)

おしゃれですね :heartpulse: 

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

# <i class="fa fa-fw fa-download"></i> 導入

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

[dropzonejs-rails](https://github.com/ncuesta/dropzonejs-rails) という Gem が公開されているので、まずはそれをインストールします。

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

```
$ bundle install
```

そして `application.js` と `application.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](http://apidock.com/rails/ActionView/Helpers/FormTagHelper/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](http://apidock.com/rails/ActionView/Helpers/FormTagHelper/file_field_tag) の第 1 引数、つまり input 要素の name 属性は JavaScript 側で明示する必要があります。さもないと `attachment[file]` が `file` になってしまいます。そのため、実際は `file_field_tag` の引数は任意の値で構いません。

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

```coffeescript
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` オプションは [公式サイトの説明](http://www.dropzonejs.com/#configuration) に載っておらず、[GitHub の Issue](https://github.com/enyo/dropzone/issues/30) で知りました。

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

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

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

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

以上で実装は完了です。

# <i class="fa fa-fw fa-wrench"></i> おまけ

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

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

```coffeescript
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>' # このオプションを追加します。
```

```scss
// アップロード後も dictDefaultMessage の文言を表示したままにします。
.dropzone.dz-started .dz-message {
  display: block;
}
```

# <i class="fa fa-fw fa-book"></i> 参考

* 公式サイト
    * [Dropzone.js](http://www.dropzonejs.com)
* Rails アプリケーションへの導入
    * [Upload Multiple Files using DropzoneJS in Rails](http://luanotes.com/posts/upload-multiple-files-using-dropzonejs-in-rails)
* 追加パラメータを渡す方法
    * [Dropzone.js > Issue #30 : Additional data](https://github.com/enyo/dropzone/issues/30)
* プレビューを非表示にする方法 
    * [Dropzone.js > Issue #175 : upload without preview](https://github.com/enyo/dropzone/issues/175)