How to customize attachment_fu file names
For uploading files, it's pretty hard to beat attachment_fu. But it can be overkill for smaller projects.
One issue is that attachment_fu uses id partioning. This is a great way to overcome native file system limitations when you have more than 32,000 attachments. By segmenting files into different directories, you can have millions of attachments, if necessary. Empahsis on "if necessary". It usually isn't.
Also, attachment_fu preserves original filenames. While this make sense for many projects, sometimes you need to have control over the naming of attachments.
Since a lot of people use Mike Clark's excellent File Upload Fu tutorial, let's use that as our starting point for customizing file names.
If we complete the tutorial, here's how attachment_fu will store our first image upload:
public/mugshots/0000/0001/chunkybacon.png
public/mugshots/0000/0001/chunkybacon_thumb.png
Hmmm, not bad. But I'd like to customize things:
- Images should be stored in
public/images/ - Thumbnails should be organized by size
- ID partioning (
0000/0001/) should be disabled - Images should be renamed with the Mugshot id
So let's open up our Mugshot model and tweak it a bit.
class Mugshot < ActiveRecord::Base
has_attachment :content_type => :image,
:storage => :file_system,
:max_size => 500.kilobytes,
:resize_to => '320x200>',
:thumbnails => { :thumb => '100x100>' },
:path_prefix => 'public/images/mugshots'
validates_as_attachment
def full_filename(thumbnail = nil)
file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix]
case self.thumbnail
when "thumb"
File.join(RAILS_ROOT, file_system_path, 'thumb', thumbnail_name_for(thumbnail, self.parent_id))
else
File.join(RAILS_ROOT, file_system_path, 'fullsize', thumbnail_name_for(thumbnail, self.id))
end
end
def thumbnail_name_for(thumbnail = nil, asset = nil)
extension = filename.scan(/\.\w+$/)
return "#{asset}#{extension}"
end
end
Now, when we upload an image, it will be stored like so:
public/images/mugshots/fullsize/2.png
public/images/mugshots/thumb/2.png
How does this work?
Well, first, we customize the :path_prefix value in has_attachment to set the base location of our files.
Second, we override the full_filename method to force attachment_fu to save each thumbnail type into its own directory. This way, all large thumbnails are stored in images/mugshots/fullsize and all small thumbnails are stored in images/mugshots/thumb. (By default, attachment_fu stores all thumbnail sizes for an object in a single directory.)
Lastly, we override the thumbnail_name_for method to customize the filename to our liking... in this case, the file name will consist of the parent mugshot id, plus the original file's file extension.
That's all we need to do... now our files are stored exactly where we want them!
(Thanks to AirBlade Software for showing the way.)
Extra credit
If your attachments belong to another model (like a user), you'll need to use a before filter to associate the parent model id with the thumbnail you wish to store.
before_thumbnail_saved do |record, thumbnail|
thumbnail.user_id = record.user_id
end
You'll also need an extra migration to store the parent id. (Don't use the parent_id field... that's reserved by attachment_fu.)
Extra, extra credit
What if you want three sizes? Large (original image), medium, and small?
has_attachment :content_type => :image,
:storage => :file_system,
:max_size => 500.kilobytes,
:thumbnails => { :small => '100x100>', :medium => '200x200>' },
:path_prefix => 'public/images/mugshots'
validates_as_attachment
def full_filename(thumbnail = nil)
file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix]
case self.thumbnail
when "small"
File.join(RAILS_ROOT, file_system_path, 'small', thumbnail_name_for(thumbnail, self.parent_id))
when "medium"
File.join(RAILS_ROOT, file_system_path, 'medium', thumbnail_name_for(thumbnail, self.parent_id))
else
File.join(RAILS_ROOT, file_system_path, 'large', thumbnail_name_for(thumbnail, self.id))
end
end

It's works with acts_as_attachment?
Not to my knowledge, Leonardo.
attachment_fu is a significant rewrite of acts_as_attachment, so a lot of the codebase has changed. You should consider upgrading your application, if possible.
If you would like to resize images based on whatever, (here the params[:adtype].keys of the AdsController having a certain value) you can do this:
1. Add a custom version of this to your model
2. Add this to attachment-fu.rb (after the definition for "has_attachment"
This wasn't working for me. Calling public_filename(:thumb) was always returning the full version since full_filename() is no longer honoring the thumbnail parameter.
This does seem to work for me, however.
Hi!
I think that customizing filenames should be in attacment_fu by default, but it seems that the people at TechnoWeenie no longer develop this plugin (at least for the seven past months). I had made my approach to this topic in a different way than you.
Basically, I hook the filename itself before the whole attachment being saved. This is made with a simple method and before callback. Works well when we are keepping the filename bound to its owner model's name or whatever.
For creations:
The example above could be a before_add statement in a model association, which is the case I are actually using in muy application. Remember that in Rails versions prior to 2.0 the before_add statement only works if you do model.association << object
For updates:
And that's all. Enjoy it!
Great article!
I am struggling a bit with custom directories though. My assets are called "mugshots" and they belong_to :artist. I am trying to hook this up so that the shots get saved in a folder with the artists name.
http://pastie.caboo.se/123257
I can save the main image successfully in the artist folder, but oddly enough the thumbnails are still defaulting to numbered folders.
Any ideas?
Hey,
Thanks for the post about the extra credit: like if your attachments belong to another model (like a user) and you need to store the photo_id but I had a question about this that I can not seem to find the answer to.
Basically I am building a social network where you have to upload images and want to store the photo_id of the image into different models. For example, a Users Profile would be one, a Group would be another, and possible even a blog could have its own image. I was looking for a DRY method that would allow me to easily store these photo_id once they upload the photo and update the model with these photo_id.
So you said you can use:
But how does this work if you have 3 models you need to store the id of the photo you just uploaded? Or if anyone else has any ideas, they would be greatly appreciated.
John,
Polymorphic Associations is what you'd need. Look into that.
Hey, Thanks for the post about the extra credit: like if your attachments belong to another model (like a user) and you need to store the photo_id but I had a question about this that I can not seem to find the answer to. Basically I am building a social network where you have to upload images and want to store the photo_id of the image into different models. For example, a Users Profile would be one, a Group would be another, and possible even a blog could have its own image. I was looking for a DRY method that would allow me to easily store these photo_id once they upload the photo and update the model with these photo_id. So you said you can use before_thumbnail_saved do |record, thumbnail| thumbnail.user_id = record.user_id end But how does this work if you have 3 models you need to store the id of the photo you just uploaded? Or if anyone else has any ideas, they would be greatly appreciated.
I wanted to save all files in a single directory yet change the names.. but gave me so many errors since thumbnail does not exist on creation time.. and we have to use self.thumbnail. did some mods to the code and hope this would be the best place to post the changes (i also got the inspiration to modify the code from here).. thanks for great tips.
modified code will hopefully work for any thumbnail. doesnt have to specify "thumb1", "thumb2" trying to minimize the code size and keeping it generic as possible.
I'd been playing with this for a while. I needed unique filenames so that i could use paranoid delete and based on your example i chose this naming scheme: public/images/photos/small/parent_id.extension. One of my goals was to make image tags easy within the app. I wanted to be able to write something like image_tag("photos/large#{photo.id}#{photo.extension}").This works for me:
I hope this is helpful to anyone searching for a way to make unique filenames with attachment_fu
overwrite thumbnail_name_for(thumbnail = nil) in attachment_fu.rb to change the name stored in the database. plugins are loaded when the server starts, so don't make the mistake i did and spend an hour wondering why your changes aren't working: restart the server.
nicolas b if this works for you it should work for me right I've got a few errors here m8!
Anyway if you could pasting the model file I would like so greatfull!
Despite correctly renaming the file itself and saving it in the correct location, the name of the file in the 'filename' field in the database doesn't get updated...it stays the same as the name of the file itself instead of getting renamed.
Any idea how to get the filename field to update?
For reference, here's my model:
New comments are disabled right now. Once we finish migrating this blog, we'll restore them.