Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Paperclip & Multiple File Uploads

I am looking for a solution to give the user the ability to upload multiple images through one file_field. I have looked in to options such a Jquery File Upload and Uploadify but have yet to come across good examples with a working solution.

I already have multiple images setup,

 has_attached_file :asset,
                   :styles => { :large => "640x480", :medium => "300x300", :thumb => "100x100" },
                   :storage => :s3,
                   :s3_credentials => "#{Rails.root}/config/s3.yml",
                   :path => "/:contributor_id/:listing_name/:filename"

Right now I am displaying 5 individual file_fields

def new
  @listing = Listing.new
  5.times {@listing.assets.build }

  respond_to do |format|
    format.html # new.html.erb
    format.json { render json: @listing }
  end
end

I would like to have

<%= f.file_field :asset, :multiple => true %>

That allows the user to select multiple files in their file browser. But how can I process these with a nested model? And get them to upload.

like image 354
kcollignon Avatar asked Jun 06 '12 20:06

kcollignon


People also ask

What is Paperclip in Rails?

Paperclip is an easy file attachment library for Rails Applications. Attached files are saved to the file system, database or cloud and referenced in the browser by an easily understandable specification.

How do you use a paperclip in rails?

Setting Up PaperclipTo set up Paperclip, first we need to install the ImageMagick dependency. Paperclip uses ImageMagick to resize images after upload. If you are using another system, you can get download and install instructions from the ImageMagick website. Run bundle install to finish it up.

Is paperclip deprecated?

x and the application still uses Paperclip to manage your file attachments, first you'll need to replace that gem because Paperclip was deprecated in favor of Active Storage after Rails 5.2 was released.


3 Answers

So there are a few issues here.

First, Paperclip's has_attached_file method isn't an association to many files. It looks like you're trying to build an "asset" as if it's a Rails association. All Paperclip does is put a couple of fields into your table to store some meta-data about the file and you get one attached file per declaration of has_attached_file. If you want to attach 5 files, you would need to do something like:

has_attached_file :asset1
has_attached_file :asset2
has_attached_file :asset3
has_attached_file :asset4
has_attached_file :asset5

OR, alternatively, you could create another model just to store the files. For example:

class Listing < ActiveRecord::Base
  has_many :assets
end

class Asset < ActiveRecord::Base
  belongs_to :listing
  has_attached_file :picture
end

That way, you could have multiple assets attached to one listing (you didn't say what the original object was so I just called it "listing").

Second, there is no such thing as a multiple file upload in HTML (and, as such, the file_field method doesn't take a :multiple => true argument. You'll have to use something beyond Rails built-in form handling if you want multiple-file upload. Uploadify is a decent choice (that I've used before). There is a gem that will transform file fields to use uploadify (and will support the :multiple => true syntax that you want): https://github.com/mateomurphy/uploadify_rails3/wiki. However, I cannot vouch for how good it is.

My advice would be to start step-by-step. Uploading via Flash to Rails can be a complicated process that involves dealing with the CSRF meta-tag and other fields in your form. Start by making a form that allows a user to upload one file and stores it through Paperclip. Then maybe break the has_attached_file declaration into another model so that you can have 1 or many files associated with a model (as shown in the multi-model code block above). Then try adding Uploadify or another alternative. Ernie Miller has a decent tutorial on integrating Uploadify: http://erniemiller.org/2010/07/09/uploadify-and-rails-3/.

To start, remember that has_attached_file can only attach one file. When you try calling @listing.assets there is no "assets". There is an asset. You need to create a separate model yourself and use Rails' associations if you want multiple files.

like image 177
sph Avatar answered Oct 10 '22 19:10

sph


Accepted answer says there is no such thing as a multiple file upload in HTML.

<%= f.file_field :files, multiple: true %>

This allows you to select multiple images and send them as an array.

If you have the relationship Dog has_many Images and Image has_attachment :file, do this to get multiple images to upload at once:

In your html.erb

<%= form_for @dog, html: { multipart: true } do |f| %>
  <%= f.file_field :files, accept: 'image/png,image/jpeg,image/gif', multiple: true %>
<%= end %>

In your controller

def dog_params
  params.require(:dog).permit files: []
end

In your Dog model

def files=(array = [])
  array.each do |f|
    images.create file: f
  end
end

This is assuming you're already able to upload one image but want to upgrade to multiple images at once. Notice that wait time will increase.

To help reduce wait time, peep my post on this question related to speed uploading.

like image 34
Cruz Nunez Avatar answered Oct 10 '22 17:10

Cruz Nunez


Here is a full example of multiple file uploads. Here a user has_many uploads. Each upload model has an avatar which represents the file attachment. Ultimately: we are creating many uploads when we create the user.

The Models

#models/user.rb
class User < ApplicationRecord
  has_many :uploads

  def files=(array_of_files = [])
    array_of_files.each do |f|
      uploads.build(avatar: f, user: self)
    end
  end
end


#models/upload.rb
class Upload < ApplicationRecord
  belongs_to :user

  has_attached_file :avatar
  validates_attachment_content_type :avatar, :content_type => ["image/png"]
end

The form:

# views/users/_form.html.erb
<%= form_with(model: user, local: true) do |form| %>

  ...

  <div class="field">
    <%= form.file_field :files, multiple: true %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

The Controller

class UsersController < ApplicationController
  before_action :set_user, only: [:show]

  def show
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to @user, notice: 'User was successfully created.'
    end
  end

  private
    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      params.require(:user).permit(:name, files: [])
    end
end

User#show

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
</p>


<h3>Uploads</h3>
<div>
  <% @user.uploads.each do |upload|  %>
    <div>
      <%= link_to upload.avatar.url do%>
        <%= upload.avatar_file_name %>
      <% end %>
    </div>
  <% end %>
</div>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>
like image 2
Neil Avatar answered Oct 10 '22 17:10

Neil