レイルズで複数の添付ファイルを1画面でアップロードする

rails(4.2.1), jquery-fileupload-rails (0.4.5)

jQuery-File-Uploadを使う。
https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin

1投稿に対して複数の添付ファイルをつける画面作る。

$ ->
  $('#fileupload').fileupload
    url: '/post_attachments.json'
    add: (e, data) ->
      $div = $("<div name='#{data.files[0].name}'>#{data.files[0].name}</div>")
      $("#filelist").append($div)
      data.context = $div

      if data.files[0].size > 10000000
        $div.text($div.text() + "[サイズオーバー]")
        $div.css({ color: 'red' })
      else
        data.submit()

    done: (e, data) ->
      $.each data.response().result, (_, item) ->
        $('#fileupload').append(
          $("<input type=hidden name=temp_post_attachment_ids[] value=#{item['post_attachment_id']}>")
        )
        data.context.text(data.context.text() + "[ok]")
        data.context.css({ color: 'green' })
class PostAttachmentsController < ApplicationController
  def create
    attachments = params.permit(attachment: [])[:attachment].map do |f|
      x = PostAttachment.create(attachment:  f.tempfile)
      { post_attachment_id: x.id,
        attachment_file_name: x.attachment_file_name,
        original_filename: f.original_filename
      }
    end
    render json: attachments
  end
end

addコールバックでは、サーバに送信される前の状態なので、ここでサイズチェックできるの便利。
data.contextプロパティにエレメントを代入しておくと、アップロードが完了した時も同じファイルのcontextプロパティを参照できるのでテキストの更新が楽にできる。便利。
addコールバックの後は、PostAttachmentsController#createがリクエストを受け取る。
ファイルの保存が成功すると、doneコールバックはが発火する。この中ではformタグ内にhiddenタグをどこどこ追加してる。
ちなみにdata.submit() されたファイルだけdoneコールバックが呼ばれる。これも便利。

= form_for @post, html: { multipart: true, class: :post_form, id: :fileupload } do |f|
  .form-group
    = file_field_tag "attachment[]", multiple: true
  #filelist.well.alert.alert-notice
  = f.submit class: 'btn btn-primary'

file_field_tagヘルパー でファイル選択のダイアログを出して、選択されたファイルは、jQuery-File-Uploadが横取りするので`同期通信`ではこのinputタグには値は設定されない。

class PostsController < ApplicationController
...
  def create
    @post = Post.new(post_param)
      if @post.save
        PostAttachment.where(
          id: params[:temp_post_attachment_ids], post_id: nil
        ).update_all(post_id: @post.id)
      else
        ...
    end
end

一時的にアップロードしているのでテーブルの定期的な掃除は必要そう。
これでドラック & ドロップでのアップロードもできるし複数アップロードも対応してる。便利。