Catchy title!
When you want to upload and resize images in Rails, attachment_fu is your best bet. Mike Clark wrote a good tutorial explaining how to get started.
If you are storing the images on the file system, attachment_fu puts any given full size image and its thumbnails in the same directory (with different filenames). For example:
photos/0000/0001/piccie.jpg
photos/0000/0001/piccie_small.jpg
You can change the path and customise the filename by overriding the full_filename method in your model. The original implementation is:
def full_filename(thumbnail = nil)
file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
end
This is how it works:
The file\system\path is set to your model’s (or your thumbnail model’s) path_prefix. By default this is the model’s table name under public. So if your model is Photo, file\system\path becomes public/photos.
The following line is more involved.
thumbnail\name\for(thumbnail) returns the image’s filename. If thumbnail is nil, the original filename is returned —– e.g. piccie.jpg. If a value is given, such as small, it returns the thumbnail’s filename —– e.g. piccie_small.jpg.
The partitioned_path method returns a path based on the image’s database ids such that your file system won’t be brought to its knees by overflowing directories.
The File.join line concatenates these components to give a path like /path/to/your/app/public/photos/0000/0001/piccie.jpg.
So far, so good. You can easily change the paths to store your images wherever you like, bearing in mind that a full size image and its thumbnails will be stored in the same directory.
But what happens if you want to store full size images and their thumbnails in different directories? It’s not quite as easy as it first seems.
You might think you could branch on the thumbnail argument like this:
def full_filename(thumbnail = nil)
file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
if thumbnail
File.join(RAILS_ROOT, file_system_path, 'thumbnails', *partitioned_path(thumbnail_name_for(thumbnail)))
else
File.join(RAILS_ROOT, file_system_path, 'fullsizes', *partitioned_path(thumbnail_name_for(thumbnail)))
end
end
But it turns out that the other parts of attachment_fu don’t pass in thumbnail when you might expect and all the images end up in the fullsizes directory path.
This, however, does the trick:
def full_filename(thumbnail = nil)
file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
if thumbnail.blank? && self.thumbnail.blank?
File.join(RAILS_ROOT, file_system_path, 'fullsize', *partitioned_path(thumbnail_name_for(thumbnail)))
else
File.join(RAILS_ROOT, file_system_path, 'thumb', *partitioned_path(thumbnail_name_for(thumbnail)))
end
end
The point to notice is that thumbnail is an argument to the method while self.thumbnail is an attribute of your model. They are not the same.
It reminds me of that baffling joke told by ontologists: “What’s the difference between a duck? One of its legs are both the same.” Except the other way around.
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:
#...
include ObjectSpace
# change :ad to whatever model your image belongs to.
belongs_to :ad
def self.find_size
# Change AdsContoller to the controller of your model and params[adtype].keys to
# whatever value you want to test for.
if adtype = ObjectSpace.each_object(AdsController) {|a| p a.params[:adtype].keys unless a.params[:adtype] == nil}
@size = case adtype
# customize your sizes and conditions
when 1
'150x100'
when 2
'200x150'
when 3
'250x200'
else
'250x250'
end
end
return @size || false
end
#modify this as you normally would, but without the :resize_to parameter
has_attachment :content_type => :image,
:storage => :file_system,
:max_size => 500.kilobytes,
:path_prefix => "public/images/"
validates_as_attachment
#...
# ...
# this allows for dynamic resizing written in the model itself - PS
options[:resize_to] = self.find_size if self.respond_to?(find_size) and find_size != false
# ...
Paul • 24 August 2007
Subclassing does the trick too :
class Asset < ActiveRecord::Base
has_attachment :thumbnail_class=>Image,
:file_system_path => '/downloads',
:content_type=>:image,
:thumbnails=>{:thumb=>'80', :big=>'600x480'},
:max_size => 10.megabyte
end
class Image < Asset
has_attachment :storage => :file_system,
:processor=>MiniMagick,
:content_type=>:image,
:file_system_path => 'public/images/photos'
end
chears
charly • 12 September 2007Hi there,
I see this post is quite old, but i'm new to ruby on rails now. I used your post to learn how to change the full_filename, and i did with success BUT there a major thing that you didn't mention in your post and i only realized later:
it changes the filename, but it doesn't change the filename on the database!
the file in the filesystem is correct, but the database still carries the old file name
do you know what do i have to do to correct such issue?
i'm really strugling here
i already tried adding after File.join:
write_attribute :filename, my_nem_filename
with no success!
thanks.
Marco • 9 May 2008Marco, the article describes how to change the path to your file, but not the file's name. I realise now I didn't make that clear, so sorry for the confusion.
You can set the actual filename by overriding the uploaded_data method in your model, like this:
def uploaded_data=(file_data)
super
self.filename = 'your_filename_here'
end
Hope that helps.
Andy Stewart • 12 May 2008
Hey great work man. I was encountering the same issue and your post did the trick. Thanks a ton man. Keep up the good work. Great code.
dubaimacha • 21 August 2007