Ruby On Rails Polymorphic Paperclip Plugin Tutorial
This tutorial is an extension of the paperclip tutorial I put up last week. This time we are going to take advantage of polymorphic paperclip. Polymorphic paperclip utilizes separate tables (an assets table and an attachings table) to track your attachments. This allows for an unlimited number of attachments per item per model. In the first tutorial I showed you how to attach just one item, although you could add more columns to your table to handle additional attachments this way is a lot more flexible. While this plugin is not perfect, there is a lot of room for improvement, its a great starting point. Perhaps a fork is on its way from me…
Project Page: http://github.com/heavysixer/paperclippolymorph/tree/master
First things first, lets install the plugin:
script/plugin git://github.com/heavysixer/paperclippolymorph.git |
Lets setup our new migration that is needed. After reading the rdoc, I noticed there was a new generator installed. Great, this makes things even easier. Lets verify that the generator is available for use, first run:
script/generate |
This should produce a list of all generators available for use, my list below might vary from yours depending upon what plugins you have installed…
Installed Generators Plugins (vendor/plugins): authenticated, forgot_password, open_id_authentication_tables, paperclip, polymorphic_paperclip, roles, rspec, rspec_controller, rspec_model, rspec_scaffold, upgrade_open_id_authentication_tables Builtin: controller, integration_test, mailer, migration, model, observer, plugin, resource, scaffold, session_migration |
As you will notice there is a polymorphic_paperclip generator, go ahead an run it as follows:
script/generate polymorphic_paperclip |
Before you go and run rake db:migrate, open up the migration and modify the assets_count to attachings_count – it seems there is a minor bug. I notified the author of the plugin and will submit a patch via git. Now its OK to run rake db:migrate
Now we have the migration generated and the tables have been added to your database. Next we have to add (or change if you already have paperclip setup) the model where you want to have attachments by adding acts_as_polymorphic_paperclip. As an example, I posted my documents model below.
class Document < ActiveRecord::Base # Document Belongs To A User belongs_to :user # for paperclip (polymorphic) acts_as_polymorphic_paperclip # Validations ... |
Note: Ideally I would like to be able to override the styles settings that are set in the plugins assets.rb, however I went ahead and hard coded them there to fit my needs. Example:
class Asset < ActiveRecord::Base has_many :attachings, :dependent => :destroy has_attached_file :data, :styles => { :thumb=> "100x100#", :small => "150x150>", :medium => "300x300>", :large => "500x500>", :xlarge => "600x600>", :xxlarge => "800x800>" } |
For now, lets move onto the views where you are going to allow attachments. We are just going to have one upload, perhaps in another tutorial we will look at handling multiple uploads.
You need to make sure you put the html => { :multipart => true } in both your edit and new views for the model you are working with. Example in my case:
<% form_for(@document,:html => { :multipart => true }) do |f| %> <%= f.error_messages %> <%= render :partial => 'form', :locals => { :f => f } %> <% end %> |
Next you need to add the file upload field to your _form or edit/new views.
<p> Attach a file or image <br /> <%= f.file_field :data%> </p> |
Now lets go ahead and make these attachments viewable in the document. For the edit view I added the following:
<p><%= image_tag asset.url(:medium) %></p> <p>Tiny: <%= asset.url(:tiny) %><br /> Small: <%= asset.url(:small)%><br /> Medium: <%= asset.url(:medium)%><br /> Large: <%= asset.url(:large)%><br /> XL: <%= asset.url(:xlarge)%><br /> XXL: <%= asset.url(:xxlarge)%><br /> Original: <%= asset.url %> </p> |
Other Notes:
You can use this to attach an attachment if you were going to use a different view, for example an upload view with @document.assets.attach(@asset)
You can nuke an attachment by either calling @document.assets.detach or @document.assets.detach(@asset) depending upon how you are going about dealing with removing attachments. @document.assets.detach will nuke ALL attachments associated with that document, @document.essay.assets.detach(@asset) will nuke just that asset you are referencing.
rohandey
Is there a facility for uploading images to S3 as this feature is there in attachment_fu
John Burmeister
Yes, its baked right in -> http://github.com/thoughtbot/paperclip/tree/master/lib/paperclip/storage.rb
heavysixer
Hey great tutorial. Give me a patch for the bug you found and I’ll patch the plugin.
Later,
Mark
Tom
Shouldn’t line one be
script/plugin install git://github.com/heavysixer/paperclippolymorph.git
ryan
if anyone is getting some weird errors after installing polymorphic paperclip… few people mention you ALSO need regular old paperclip installed too. it’s in the readme file included in polymorphic paperclip. regular paperclip can be installed via ‘script/plugin install git://github.com/thoughtbot/paperclip.git’
cheers.
John Burmeister
Yes you also need paperclip installed, its a dependency…
EH
Some of this doesn’t make sense, and I’m getting some errors. Do you have to create an Assets model by hand to match up with the table created in the migration?
ron
I got this when uploading an image
undefined method `data_updated_at=’ for #
Changing the assets.updated_at column in the db to assets.data_updated_at seemed to fix it.
Also had to change assets_count to attachings_count
EH
Nevermind. It just took a closer reading of:
I would like to be able to override the styles settings that are set in the plugins assets.rb, however I went ahead and hard coded them there to fit my needs
So what you have to do is edit the plugin’s file in:
/vendor/plugins/paperclippolymorph/lib/asset.rb
Will Merrell
Thanks for this plugin, I have it working and it is just what I needed.
However, I cannot figure out how to remove an asset once I have added it. There is a mention at the end of this article that suggests using detach, but I have not been able to get actual code to work. Could you say a bit more about that part of it?
— Will
EH
Will:
class AssetsController < ApplicationController
def destroy
@asset = Asset.find(params[:id])
@asset.delete
redirect_to :back
flash[:notice] = "Asset deleted"
end
end
Pablo Targa
Sorry but I’m having problems with multiple upload. I put this in a partial and in form I call 3.times
Foto
it returns me Asset(#18805070) expected, got HashWithIndifferentAccess(#2722440)
….
Great [post, plugin] thnx!!!
Sorry about the english… brazillian boy 😉
Pablo Targa
Hello everybody!!!
In my previous comment I paste the code in code tag… but aparently its strip out my code so here it comes again:
My controller is Events, so the event has many photos… and I want to make multiple upload… I try the follow
= file_field_tag “event[assets][][data]”
pls!! I’m begging rs 🙂
EH
Dude, you have to add the “install” command in the very first snippet.
thaniyarasu
ultimate plugin….
EH
Can you rewrite the view code to account for the Document model that begins the example?
Joe
Anyone else getting “undefined local variable or method `asset’ ” when they try to use the display code provided? Why would you put that code in the “Edit” view for the object?
Let’s say you have a blog post using model Post.
The model I added “acts_as_polymorphic_paperclip”.
To the views/posts/edit.html.erb view I added the view code provided: “”
As I added it, I thought it didn’t make sense…after all, what is “asset”? I thought it was some kind of plugin magic. But I guess not?
Joe
To answer my own question:
Joe
It cut out my code…trying again:
I was able to work this out by using the following:
Joe
Last try, this has html tags stripped off:
for asset in @post.assets
image_tag asset.url(:medium)
end
jeff
> Note: Ideally I would like to be able to override the styles settings that are set in the plugins assets.rb, however I went ahead and hard coded them there to fit my needs.
Glancing at the source code, it looks to me that acts_as_polymorphic_paperclip accepts two optional arguments, one of them being :styles hash.
Nemo
I agree with EH. It would be beneficial if you could elaborate on the displaying for the model that acts as polymorphic paperclip. In this case the Document model.
Thanks, and nice tutorial.
dimir
Will Merrell:
I’m having the same problem deleting an image. Could you give an example of detaching the asset from a document view where all the assets (images) are listed? I realize I should call @document.assets.detach(@asset) but I don’t know how to create a link (or a button) clicking which user should get confirmation (for example using :confirm in link_to) and asset get deleted.
Did you solve that problem Will? Or could somebody give me an example of such link or button?
Thanks in advance,
dimir
Dan
Great post, thanks!
Also, thanks Joe for your comments. I was having the same problem.
John Burmeister
I’ll prob post and update to this soon, I have a project where I’ll be using paperclip again shortly.
Dan
Did you ever post about how to handle multiple uploads on the same form?
Dan
I’m guessing accepts_nested_attributes_for is probably the way to go.
LeeRichmond
firstly great tutorial this works amazingly 🙂
I am currently working on a way of being able to upload multiple attachments using the polymorphic paperclip, I dont suppose you have any pointers on how I would achieve this?
Andreas Ludewig
Many thanks for this tutorial.
Some questions:
1)What is the structure of the join table and the join model? That could be interesting, too.
2)Shouldn´t it be
{ :multipart => true }) do |f| %>
in the form (instead @ document)??
3) I think there is the “install” command missing in the installing call.
Many Thanks so far.
Andreas
Andreas Ludewig
Oh, sorry I diddnt get the hole message submitted: I thought about @asset instead @document.
Kristian Mandrup
How can I create a named scope to get the first asset referenced by a model? fx:
class User
acts_as_polymorphic_paperclip
has_one :main_pic,
:class_name => ‘Asset’
>> User.first.main_pic
Mysql::Error: Unknown column ‘assets.user_id’ in ‘where clause’: SELECT * FROM `assets` WHERE (`assets`.user_id = 1) LIMIT 1
I guess I could create a method, but not as elegant or flexible (can’t be nested with other scoped etc.)
def first_pic
assets[0]
end
Any ideas?
Kristian Mandrup
I thought the controller logic was missing, but it turns out it works by pure magic! 😉
You have to be careful, that if you add the polymorphic association to user, then :data must be added to attr_accessible in order to allow mass assignment (or add :data manually after mass assignment in the controller method – update/create!)
Something similar to this… not sure how exactly I must confess (anyone?)
def update
@user.update_attributes(params[:user])
@user.data = params[:user[:data]])
…
end
Antoine
I’m using polymorphic paperclip to have more than one type of attachment per model object. For instance a user can have an image and a pdf. I can upload both but i want to have to separate form fields for it and put different types of attachments in different folders. what is the best way to make implement that functionality with polymorphic paperclip?
adam
I cannot get
to work. I get “ActiveRecord::RecordNotFound” when i try to view the page with that code in it.
adam
sorry…that should’ve been:
<% @project.assets.each do |asset| %>
<%= image_tag asset.url(:thumb) %><br />
<%= link_to “delete”, asset.detach(asset) %>
<% end %>
Jayme
Where can I change the accepted content file type?
rtacconi
I am not able to save any file. I always get object.assets.size = 0
Mihai
adam, found the solution to your problem?
Levi Rosol
has anyone been able to get this to work with s3? I was using paperclip with s3 for an avatar on my model, and now have this setup. when i save a file, the s3 settings i set in the asset.rb get ignored.
based on what i’ve done so far, i’m starting to thing that it really is the case that the polymorphic paperclip is doing exactly that. can anyone speak to this?
avery
@artist.assets.detach doesn’t work quite the way described. You can’t call the method ‘detach’ on an array. Perhaps something like this is better:
@artist.assets.each do |asset|
@artist.assets.detach(asset)
end
avery
Adam,
ashis
how do i enable multiple uploads?