danialfarid / ng-file-upload

Lightweight Angular directive to upload files with optional FileAPI shim for cross browser support
MIT License
7.87k stars 1.59k forks source link

Using ng-file-upload with Rails carrierwave gem to upload multiple files #930

Closed DaMinaup6 closed 9 years ago

DaMinaup6 commented 9 years ago

I'm trying to combine ng-file-upload and carrierwave to upload multiple files, but the controller on server side receives only one file (the last item of the selected files).

Client side

html

<button type="file" ng-model="files" ngf-select ngf-multiple="true">Upload</button>

js

var upload = function (files, content) {
    return Upload.upload({
        url: 'MY_CONTROLLER_URL',
        file: files, // files is an array of multiple files
        fields: { 'MY_KEY': 'MY_CONTENT' }
    }).progress(function (evt) {
        var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
        console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name);
    }).success(function (data, status, headers, config) {
        console.log('files ' + config.file.name + ' uploaded. Response: ' + data);
    }).error(function (data, status, headers, config) {
        console.log('error status: ' + status);
    });
};

console.log(files) prints Array[File, File, ...] (Browser: FireFox). So on client side it does get the selected files.

Server side

posts_controller.rb

    def create
        @post = Post.new(post_params)
        @post.attaches = params[:file]
        @post.save
        render json: @post
    end

private
    def post_params
        params.require(:post).permit(:content, :file)
    end

where @post.attaches is the attachments of a post (reference).

I want to store an array of files into @post.attaches, but params[:file] contains only one file of the selected files. puts params[:file] prints:

#<ActionDispatch::Http::UploadedFile:0x007fddd1550300 @tempfile=#<Tempfile:/tmp/RackMultipart20150812-2754-vchvln.jpg>, @original_filename="Black-Metal-Gear-Rising-Wallpaper.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"file\"; filename=\"Black-Metal-Gear-Rising-Wallpaper.jpg\"\r\nContent-Type: image/jpeg\r\n">

This shows that there is only one file in params[:file]. How could I solve this problem?
Here is my post.rb model and attach_uploader.rb (created by carrierwave) for reference if needed: post.rb

class Post < ActiveRecord::Base
    mount_uploaders :attaches, AttachUploader
end

attach_uploader.rb

class AttachUploader < CarrierWave::Uploader::Base
    storage :file
    def store_dir
        "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
    end
end

and @post.attaches column in database posts is added by

rails g migration add_attaches_to_posts attaches:json
danialfarid commented 9 years ago

Check the browser's network tab and if it is sending all the files in the request body then it's a server side issue, you can create a stackoverflow question.

DaMinaup6 commented 9 years ago

I finally found a way to solve my problem. Thanks for @danialfarid's awesome ng-file-upload.

My problem was I couldn't send all selected files. My solution was

var upload = function (files) {
    var names = [];
    for (var i = 0; i < files.length; ++i)
        names.push(files[i].name);
    return Upload.upload({
        url: '/api/v1/posts',
        file: files,
        fileFormDataName: names
    });
}

Then in my rails controller.rb

file_arr = params.values.find_all { |value| value.class == ActionDispatch::Http::UploadedFile }
if @post.save
  unless file_arr.empty?
    file_arr.each { |attach|
      @attach = Attach.new
      @attach.filename = attach
      @attach.attachable = @post
      @attach.save
    }
  end
  render json: @post
end

I created an array to store all my files from params.

I tried to use a column with mount_uploaders of carrierwave to store an array of files, but it didn't work. So I create a file table called attaches to store my files

class CreateAttaches < ActiveRecord::Migration
  def change
    create_table :attaches do |t|
      t.string :filename
      t.references :attachable, polymorphic: true
      t.timestamps null: false
    end
  end
end

where attachable is used to store the post id and type. (Here my attachments belong to some post in my forum.)
Here is some details about setting if needed attach.rb (model)

class Attach < ActiveRecord::Base
  mount_uploader :filename, AttachUploader
  belongs_to :attachable, :polymorphic => true
end

post.rb (model)

class Post < ActiveRecord::Base
  has_many :attaches, as: :attachable, dependent: :destroy
end

post_serializer.rb

class PostSerializer < ActiveModel::Serializer
  has_many :attaches
end

attach_serializer.rb

class AttachSerializer < ActiveModel::Serializer
  attributes :url, :name

  def url
    object.filename.url
  end

  def name
    object.filename_identifier
  end
end

Then in html file can have a row code

<div ng-repeat="attach in post.attaches">
    <img ng-src="{{attach.url}}" type="file" height="180" width="320" accept="image/*"/>
    <a target="_self" ng-show="attach.url" href="{{attach.url}}" download="{{attach.name}}">{{attach.name}}<p></p></a>
</div>

where my default attachments are used for images.