I was having a bit of trouble with attachment_fu that took a while to figure out, so I thought I’d post my solution for the next person.
I have a Photo model that I’m using to store pictures. Since attachment_fu will automatically resize, create thumbnails, and store pictures on the file system, it was an easy choice to use it. The things I found I didn’t like about it:
- It stores thumbnails as separate records inside the photos table. This means you have to check if thumbnail.nil? each time you display the pictures, and you’ll have to check parent_id.nil? to count your photos.
- You can’t use your own model validations. For example, I wanted to use
validates_presence_of :name validates_presence_of :description
But I’d always get validation errors because attachment_fu tries to save the thumbnail attributes when it creates or resizes a thumbnail. As you can imagine, this is a major problem.
:thumbnail_class to the rescue
Mike Clark’s attachment_fu blog post mentions that you can use the :thumbnail_class argument to separate your model validations and attachment_fu’s validations. Here’s how to do it:
class Photo < ActiveRecord::Base has_many :thumbnails, :foreign_key => 'parent_id' has_attachment :storage => :file_system, :content_type => :image, :max_size => 10.megabytes, :resize_to => '640x480', :thumbnails => { :thumb => '100x100' }, :thumbnail_class => Thumbnail # Validations validates_presence_of :name validates_presence_of :description end class Thumbnail < ActiveRecord::Base belongs_to :photo, :foreign_key => 'parent_id' has_attachment :storage => :file_system, :content_type => :image end
The killer for me initially was that I wasn’t specifying has_attachment in the Thumbnail model. I always got this error:
undefined method `temp_path=' for #thumbnail:0xb69a9514
So save yourself by putting has_attachment in both models. Make sure to define any attachment_fu arguments in your Photo model, and leave the Thumbnail model bare. You’ll also want to make sure you have the normal attachment_fu schema in both models:
t.column :parent_id, :integer t.column :content_type, :string t.column :filename, :string t.column :thumbnail, :string t.column :size, :integer t.column :width, :integer t.column :height, :integer
I’ve found that everything seems to be working as normal going this route. My model validations work and the thumbnails are not polluting the photos table. If you want to find the Photo for a particular Thumbnail, keep in mind that parent_id now refers to the id in the Photo model:
t = Thumbnail.find .... # find your thumbnail p = Photo.find(t.parent_id)
And for the ultimate ease-of-use relationship, just use:
t = Thumbnail.find .... # find your thumbnail p = t.photo # get a photo p.thumbnails # get all thumbnails
This works since we defined the has_many relationship in the model.
Let me know if you have any problems with this method or if it helped you!
Greg | 08-Jan-08 at 2:03 pm | Permalink
Shouldn’t your Photo have many :thumbnails and not photos?
solipsistic | 10-Jan-08 at 10:50 pm | Permalink
Yes, yes it should. Fixed.
fulvio | 03-Feb-08 at 1:17 am | Permalink
I noticed that this works for any type of custom defined thumbnail (ie. :thumbnails { :any => “28×28>” }).
This is exactly what I’ve been after for a while.
Thanks a million!
Liam | 09-Apr-08 at 10:24 pm | Permalink
Thanks for the quick tutorial!
In the last lines you show off the nice relationship between photo and thumbnails. So for a given thumbnail you can find its photo. I can’t seem to get it work as easily the other way arround: for a given photo I’d like to find its thumbnail.
for instance:
p = Photo.find :all
p.first.thumbnails
does not work!
As an added layer of complexity, my photos belong_to :person. So for a given person I can find their photo. However I can’t figure out to include the person_id (which connects a given photo with a person) into each thumbnail database entry (to connect a given thumbnail with a person).
Any suggestions?
solipsistic | 09-Apr-08 at 11:31 pm | Permalink
@Liam
I’m not sure why your photos aren’t showing thumbnails. Mine work fine. Maybe you could describe exactly what you mean by “does not work” ? Error? Empty set?
I do the same thing you’re trying to accomplish with your Person model, except mine is called User. I do something like
using
to give me the thumbnail. It sounds like you only have one Photo per Person, so you could probably do something like
Do a
class Person def thumbnail thumbnails.first end endto make your life a bit easier and allow
This is, of course, assuming you want only one Thumbnail per Photo.
Liam | 10-Apr-08 at 5:45 pm | Permalink
Thanks for your quick response.
In my Thumbnail model I included belongs_to :person and in my Person model I had both has_one :photo and has_one :thumbnail. I got ride of belongs_to :person (in Thumbnail model) and got rid of has_one :thumbnail (in Person model) restarted the server and your example worked.
Given that I only have 1 Thumbnail per Photo I only need to do:
p.photo.thumbnails