Search code examples
ruby-on-railsangularjscarrierwaveng-file-upload

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


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 (reference)

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. On the GitHub page of ng-file-upload says it supports array of files for html5.

Server side (reference)

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 and params[:file] is sent from client side by file parameter in Upload.upload.

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]. I'm not sure if there is anything wrong with the usage of this parameter.

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

Solution

  • I finally found a way to solve my problem. Thanks for carrierwave and 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.