Rails Paperclip & Multiple File Uploads - ruby-on-rails

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.

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.

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.

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 %>

Related

Rails Paperclip Error: No handler found for

This is quite a common error and I have went through many answers but nothing worked for me. Can you please help, where I am going wrong?
Scenario: I have an item and image table and when user adds an item, he/she must add at least one image. When I save the form after attaching an image it gives an error. The code snippets and error screenshot are attached below:
item.rb
class Item < ApplicationRecord
belongs_to :user, class_name: 'User', foreign_key: :user_id
has_many :images, class_name: 'Image', foreign_key: :item_id, dependent: :destroy
accepts_nested_attributes_for :images, allow_destroy: true
end
image.rb
class Image < ApplicationRecord
belongs_to :item, foreign_key: :item_id, optional: true
has_attached_file :image, styles: { small: '64x64', med: '100x100', large: '200x200' },
default_url: '/images/:id/:filename'
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
end
items_controller.rb
class ItemsController < ApplicationController
def new
#item = Item.new
#item.images.build
#new.html.erb
end
def create
#item = Item.new(item_params.merge(user_id: current_user.id))
if #item.save
redirect_to items_path
else
render :new
end
end
private
def item_params
params.require(:item).permit(:name, :category, :qty, :cost, :description,
:city, :postal_code, :country,
images_attributes: [:id, :item_id, :_destroy, image: []])
end
end
new.html.erb
<div class="container">
<h2> <% if current_user.items.blank? %> Become a seller! <% end %> Add a new item </h2> <br>
<%= form_for #item, url: {action: :create}, html: { multipart: true } do |f| %>
<%= item_error_messages! %>
<div class="form-group">
<%= f.label :name, class:'control-label col-sm-2' %>
<div class="col-sm-10">
<%= f.text_field :name, class: 'form-control', placeholder: 'Enter name' %>
</div>
</div>
<%= f.fields_for :images, #item.images.build do |img| %>
<%= img.file_field :image, multiple: true %>
<% end%>
<br><br>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10" style="margin-bottom: 40px;">
<%= f.submit 'Save', class:'btn btn-default' %>
</div>
</div>
<% end %>
</div>
Error:
What could be the possible issue? Most of the solutions I've read suggest adding multipart: true in form tag but it is already there.
Thanks
I think you problem is related with the fact that you are trying to save more than one attached file per model. Apparently, Paperclip don't treat this case very well, so you need to do it by your own hand.
If you want to save multiples files at the same time, you can follow the accept answer in the link below that example how to achieve that:
Just a resume from the link below:
1 - To save multiple files in the above example you, do not need add
multiple: true option, it will cause an error when saving.
2 - About params: This will cause an error because the model does not know what to do with an array of images
3 - To save more than one file at the same time, you need to use your own
handler
Rails 4 multiple file attachments with Paperclip
Good luck!
#UPDATE
Ideas about to how save multiples file at once:
1 - You will need to have a form with a dynamically input for files.
So each input can get the entry for each file. You can achieve that
writing your own .js lib or using a gem like "Cocoon". You will be
using nested attributes here.
2 - The strong parameter in your controller will be accepting you Item
model and an array of files.
3 - You will have to edit your method to save your data. E.g (in your
case #item.save won't work because paperclips it's not design to
accept an array of files). So, in your Item model you will have to
write a method similar to the below:
(pseudo-code)
def save(item_attributes, array_of_files)
#item = Item.new( -- add here only Item attributes --)
#item.save
# Here you are creating a model file for each file you are saving.
# So there will be N file model for 1 item.
for file in array_of_files
#file = File.new( #item.id, file)
#file.save
end
# here you can implement transaction in case a error occurs.
# and shows the error to your client through Item model
# google how to do this (transaction and Active Record class).
end

Model returns no data ruby on rails

Im following this tutorial: https://www.devwalks.com/lets-build-instagram-in-rails-part-1/
To create a version of instagram. When I upload an image, add a caption and submit, it will redirect to the index page as expected but the data doesnt seem to have been saved. When I open the rails console and try to get the posts with Posts.first, it returns nil.
Controller:
class PostsController < ApplicationController
def index
end
def new
#post = Post.new
end
def create
#post =Post.create(post_params)
#post.save
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:image, :caption)
end
end
Model:
class Post < ActiveRecord::Base
validates :image, presence: true
has_attached_file :image, styles: { :medium => "640x"}
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
form:
<%= simple_form_for #post do |f| %>
<%= f.input :image %>
<%= f.input :caption %>
<%= f.button :submit %>
<% end %>
routes:
resources :posts
root 'posts#index'
Appreciate any ideas.
Thanks
I see a few problems here:
create will save so you don't need another #post.save.
create returns the new Post object, but you have to check if it has been saved successfully or not (via #post.persisted, or via if #post.save).
From 1 & 2, I believe your post was not saved, due to validation on image presence.
Now why it's happening? I guess your form has no multipart/form-data set that the image file was not submitted at all.
To add that to simple_form (paperclip README) :
<%= simple_form_for #post, html: { multipart: true } do |f| %>

deleting multiple files with paperclip and rails (check_box_tag)

I've used the following technique to successfully upload multiple files using paperclip (without using nested attributes)
Project.rb
has_many :photos, :dependent => :destroy
Photo.rb
has_attached_file :asset, :styles => { :medium => "300x300>" }
belongs_to :project
photos/new.html.erb
<%= form_for #photo, :html => { multipart: true } do |f| %>
<div>
<%= f.label :asset, 'Project Photos', class: 'label1' %>
<%= file_field(:photo, :asset, :multiple => true) %>
</div>
<div>
<%= f.submit "Add photos to project" %>
</div>
Photos_controller:
def create
#project = Project.find(params[:id])
#client = Client.find(params[:id])
params[:photo][:asset].each do |file|
#photo = #project.photos.build(:asset => file)
if
#photo.save.......
photos/show.html.erb
<div>
<% #project.photos.each do |p| %>
<%= image_tag(p.asset.url(:square)) %>
<%= check_box_tag 'destruction[]', p.id %>
<% end %>
Routes file:
resources :photos do
collection do
delete 'destroy_multiple'
end
end
I'm trying to create an array of id's based on checkboxes to be passed to the destroy_multiple action in the photos controller. params[:destruction] does yield the correct ids.
What would be the most efficient way of telling the destroy_multiple action to delete only those assets whose id's are in the destruction array?
Many thanks for your time.
destroy_all
Paperclip is just a link between the saved asset (image/video), and your ORM (in our case ActiveRecord).
This means that you should still be able to perform all the queries you want through your standard AR methods (destroy_all etc), with Paperclip removing the relevant assets as you require.
As such...
#config/routes.rb
resources :photos do
delete :destroy_multiple, action: :destroy, on: :collection
end
#app/controllers/photos_controller.rb
class PhotosController < ApplicationController
def destroy
ids = params[:destruction] || params[:id]
Photo.destroy_all ids if ids
end
end
Paperclip will handle the rest!

Return url from paperclip to json

I have a rails app that consists of a CMS system that I use in order to enter sights from a city to my database. I am using paperclip to upload images to amazon s3. All is working fine. Now I want my json files that an ios app will use to include the urls of the images uploaded in s3. I have seen some answers here but I cannot seem to make my code work. What I have is this..
place model
attr_accessible :assets_attributes, :asset
has_many :assets
accepts_nested_attributes_for :assets, :allow_destroy => true
def avatar_url
assets.map(&:asset_url)
end
asset model
class Asset < ActiveRecord::Base
attr_accessible :asset_content_type, :asset_file_name, :asset_file_size, :asset_updated_at, :place_id, :asset
belongs_to :place
has_attached_file :asset
validates_attachment :asset, :presence => true,
:content_type => { :content_type => ['image/jpeg', 'image/png'] },
:size => { :in => 0..1.megabytes }
def asset_url
asset.url(:original)
end
end
view code
<%= f.fields_for :assets do |asset_fields| %>
<% if asset_fields.object.new_record? %>
<p>
<%= asset_fields.file_field :asset %>
</p>
<% end %>
<% end %>
<br/>
<%= f.fields_for :assets do |asset_fields| %>
<% unless asset_fields.object.new_record? %>
<%= link_to image_tag(asset_fields.object.asset.url(:original), :class => "style_image"), (asset_fields.object.asset.url(:original)) %>
<%= asset_fields.check_box :_destroy %>
<% end %>
<% end %>
places controller
def index
#places = Place.all
render :json => #places.to_json(:methods => [:avatar_url])
end
Can anyone please help me?
In reference to the SO question you linked to (How can I get url for paperclip image in to_json), there are certain elements you'll need in order to get the image to render correctly
The problem you have is Paperclip's image method is actually an ActiveRecord object, and therefore you cannot just render it in a JSON request without doing some other stuff
The _url Method
The most important part of the process is to define the "_url" method in your asset model. This basically calls the .url function of Paperclip, allowing JSON to create the desired URL of the image on the fly (The url of the image is not an ActiveRecord object, and can therefore be sent via JSON)
As per the referenced SO question, you should put this action in your model:
#app/models/asset.rb
def asset_url
asset.url(:medium)
end
Now when you render the JSON request in your controller, you can use this type of setup:
#app/controllers/places_controller.rb
render :json => #places.to_json(:methods => [:asset_url])
Because your asset model is an associate of places, this might not work straight away. However, it's definitely in the right direction, because I can remember doing this exact thing myself
The important thing to note here, is that you're actually passing the naked "URL" of the image through JSON, not the image object itself
Update
Here's an example from our video conference demo app we made:
#app/controllers/profiles_controller.rb
def update
#profile = User.find(current_user.id)
#profile.profile.update(upload_params)
respond_to do |format|
format.html { render :nothing => true }
format.js { render :partial => 'profiles/update.js' }
format.json { render :json => #profile.profile.as_json(:only => [:id, :avatar], :methods => [:avatar_url])
}
end
end
#app/models/profile.rb
def avatar_url
avatar.url(:original)
end
So for you, I'd try this:
def index
#places = Place.all
render :json => #places.assets.as_json(:only => [:id, :asset], :methods => [:asset_url])
end
You could also try something like this:
#app/models/profile.rb
def avatar_url
self.asset.avatar.url(:original)
end

Paperclip displaying multiple file uploads

I have multiple files being uploaded using Paperclip, but I am having trouble displaying them. Here is what I'm trying:
Model(s):
class Attach < ActiveRecord::Base
attr_accessible :protocol_id, :file
has_attached_file :file,
:path => ':rails_root/public/system/attachs/files/000/000/0:id/original/:basename.:extension'
attr_accessible :file_file_name, :file_content_type, :file_file_size
validates_attachment_presence :file
belongs_to :protocol
end
class Protocol < ActiveRecord::Base
attr_accessible :current_approved, :p_irb_apn, :past_approved, :attachs_attributes
has_many :attachs
accepts_nested_attributes_for :attachs, :allow_destroy => true
end
Part of my controller:
def new
#protocol = Protocol.new
#protocol.attachs.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #protocol }
end
end
Part of my form where I store images:
<div class="field">
<%= f.label :file, "Mod" %>
<%= file_field_tag('protocol_attachs_attributes_file', multiple: true, name: "protocol[attachs_attributes][][file]") %>
</div>
And my show:
<p>
<b>Modification:</b>
<% for attach in #protocol.attachs %>
<%= link_to "Download", #protocol.attachs.url(:original)%>
<% end %>
</p>
I'm getting the same file over and over, every time I upload (even if it's a different file). Can anyone assist me with this issue?
You're iterating, but running the same code for each attachment link.
Using canonical Ruby, it would be closer to:
<% #protocol.attachs.each do |attach| %>
<%= link_to "Download", attach.url(:original) %>

Resources