18
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Jquery File Upload + CarrierWaveで爆速ファイルアップロード

Posted at

#はじめに
CarrierWaveでファイルアップロード機能を実装していましたが、数メガの写真をまとめてアップロードするとコケることがたびたびあったのでなんとかしたいと思いました。

やりたいこと

ここでやりたいことは以下の二つです。

  • アップロード中にプログレスバーを出す
  • アップロードする前にファイルを小さくして大きなファイルをアップロードしないようにする

Jquery File Uploadを使うとこの2つができそうだったのでこちらを使うことにしました。
jQuery File Upload RailsというRails用のgemもあるのでそちらを使っても良いかもしれません。
私はこちらの存在を後から知ったのでgemは使っていません。

CarrierWaveの設定方法は解説しているところがたくさんあるのでここでは割愛します。

環境

ruby 2.3.1
rails 5.0.0
carrier_wave 1.0.0beta

基本実装

基本のドキュメント

Jquery File Uploaderのセッティングは本家のWikiにあった以下の記事とデモのソースを参考にして行いましたがうまくいかないところがあったので変更点をまとめます。

Rails setup for V6 | jQuery-File-Upload (Wiki)
デモページ

※デモのソースにはblueimp.github.ioからいくつもjsを引っ張ってきていますが、これらは各jsを本家からダウンロードしてきて使いましょう。


起こった問題 ファイルのアップロードはできているのにEmpty file upload resultと出る
原因:コントローラーでセーブした際のjsonの内容が間違っている


Wikiの記事ではPictureモデルで返すjsonの内容を以下のように設定していました。

Picture.rb
  def to_jq_upload
    {
      "name" => read_attribute(:avatar),
      "size" => avatar.size,
      "url" => avatar.url,
      "thumbnail_url" => avatar.thumb.url,
      "delete_url" => picture_path(:id => id),
      "delete_type" => "DELETE" 
    }
  end

しかし参考サイトによると、このattribute名はどうも古い名前のようで、jsonの内容は以下のような形にしなければならないということでした。

{"files": [
  {
    "name": "picture1.jpg",
    "size": 902604,
    "url": "http:\/\/example.org\/files\/picture1.jpg",
    "thumbnailUrl": "http:\/\/example.org\/files\/thumbnail\/picture1.jpg",
    "deleteUrl": "http:\/\/example.org\/files\/picture1.jpg",
    "deleteType": "DELETE"
  },
  {
    "name": "picture2.jpg",
    "size": 841946,
    "url": "http:\/\/example.org\/files\/picture2.jpg",
    "thumbnailUrl": "http:\/\/example.org\/files\/thumbnail\/picture2.jpg",
    "deleteUrl": "http:\/\/example.org\/files\/picture2.jpg",
    "deleteType": "DELETE"
  }
]}

参考
jQuery File Upload “Error - Empty file upload result” - Rails Application | Stack overflow
Using jQuery File Upload (UI version) with a custom server-side upload handler

最新バージョンでのコード

Rails setup for V6の内容から変更する必要がある箇所は以下のとおりです。
他の部分は上記ドキュメントの通りでOKです。

Picture.rb
  def to_jq_upload
    {
      "name" => read_attribute(:avatar_name),
      "size" => avatar.size,
      "url" => avatar.url,
      "thumbnailUrl" => avatar.thumb.url, # 変更
      "deleteUrl" => picture_path(:id => id), # 変更
      "deleteType" => "DELETE" # 変更
    }
  end
pictures_controller.rb
  def create
    @picture = Picture.new(picture_params)
    if @picture.save
      json = Hash[files: [@picture.to_jq_upload]].to_json # 変更 旧:[@picture.to_jq_upload].to_json

      respond_to do |format|
        format.html {
          render :json => json,
                 :content_type => 'text/html',
                 :layout => false
        }
        format.json {
          render :json => json
        }
      end
    else
      render :json => [{:error => "custom_failure"}], :status => 304
    end
  end

アップロード前にクライアントサイドで写真をリサイズ

基本はこちらのWikiに Client side Image Resizing

オプションはこちら Image Preview & Resize Options

こんな感じにするとアップロード前にリサイズしてくれます。

main.js
$(document).on('ready', function() {
    'use strict';

    // Initialize the jQuery File Upload widget:
    $('#fileupload').fileupload({
        disableImageResize: /Android(?!.*Chrome)|Opera/
            .test(window.navigator && navigator.userAgent),
        imageMaxWidth: 720,
        imageMaxHeight: 480,
        imageCrop: true // Force cropped images
    });
});

サムネイルなども作る場合は、この形でJqueryFileUploadが元ファイルからweb閲覧サイズへの縮小をクライアントサイドで行い、アップロード後にCarrierWaveがサムネイルを作るといった流れになります。

nested formで複数の写真をまとめてアップロード

Spotモデルが複数のPictureモデルを子モデルとして持ちます。
だいたいこんな感じに実装しました。

spot.rb
class Spot < ActiveRecord::Base
  has_many :pictures, as: :target
  accepts_nested_attributes_for :pictures
end
picture.rb
class Picture < ActiveRecord::Base
  include Rails.application.routes.url_helpers

  belongs_to :target, polymorphic: true

  mount_uploader :file, PictureUploader

  def to_jq_upload
    {
      "name" => read_attribute(:file),
      "size" => file.size,
      "url" => file.url,
      "thumbnailUrl" => file.thumb.url,
      "deleteUrl" => picture_path(:id => id),
      "deleteType" => "DELETE"
    }
  end
end
spots_controller.rb
class SpotsController < ApplicationController
  def update
    @spot.attributes = spot_params

    if @spot.save
      json = Hash[files: [@spot.pictures.last.to_jq_upload]].to_json # ここポイント
      respond_to do |format|
        format.html {
          render :json => json,
                 :content_type => 'text/html',
                 :layout => false
        }
        format.json {
          render :json => json
        }
      end
    else
      render :json => [{:error => "custom_failure"}], :status => 304
    end
  end

  private
 def spot_params
    params.require(:spot).permit(pictures_attributes: [:file]) # ここポイント
  end
end

複数のファイルを同時にアップロードしますが、実際に挙動としては1ファイルにつき1回updateが呼び出されています。
なのでspot.picturesの中で最後に追加されたものをjsonで渡してあげれば良いということです。

viewの方はデモページのソースからデザイン以外で改変する部分は特にありませんでしたのでそちらを参照してください。

うまくいかなかったところ

解決できなかった問題ですが、何かの参考になるかもしれないので記録として残しておきます。

Deleteボタンが機能しない

Basic Plus UIバージョンで試していましたが、Deleteボタンを押すと以下のようなエラーが出てきました。

Can't verify CSRF token authenticity.
ActionController::InvalidAuthenticityToken

Ajaxでdestroyしようとすると結構面倒なんですよね。
とりあえず以下のことは試してみました。

  • button_tagにremote: trueを追加してみる → 効果なし
  • form_forにremote: true, authenticity_token: trueを追加してみる → アップロードもできなくなった

Basic Plus UIを使う場合アップロード前に予めプレビューが出ています。
この状況でアップロード後すぐにAjaxでファイルを削除機能つける必要あるかなあということで、削除前に確認も出ずにサクッと消えてしまうのもどうかなということもあり私の場合はアップロード画面でAjax削除機能はつけないことにしました。

参考
ActionController::InvalidAuthenticityToken when disable JS/Ajax request | Stack overflow

CarrierWaveの複数ファイルアップロードに対応できなかった

CarrierWaveの以前のバージョンではmount_uploaderで1カラム1ファイルの保存しかできませんでしたが、1.0.0ではmount_uploadersでArray形式のカラムに複数のファイルを保存できるようになりました。
しかし、Arrayカラムへの複数ファイルの保存のやり方がわからず以前からこのArrayへの複数ファイルの保存で別の問題があったので1カラム1ファイルにするよう変更しました。

余談:CarrierWave公式の複数ファイルアップロードで抱えてた問題
一つのカラムに複数のファイルをまとめてあげられるのは楽な面もありますが、以下の2点はかなり大きな問題だったので導入の際にはよく検討したほうがいいのではないかと思います。

  1. 各ファイルごとにアップロードしたユーザーやキャプションなどの項目をつけるのが困難
  2. まとめてアップロードして、失敗したファイルがあった場合に失敗したファイルだけど消すことができなかった

以上です

18
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?