Tuesday, August 21, 2007

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 <>
  • 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 # To validate the size of the file being uploaded

  • 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.)

Important Note :---

In order to make use of the functionality provided by attachment_fu you need to create an ActiveRecord model with at least the following attributes:

  • content_type: what sort of content you are storing. This is used by web browsers to know how to present this information to users (open an external application, show embedded using a plugin, etc).
  • filename: a pointer to the image location
  • size: the size in bytes of the attachment

Wednesday, August 8, 2007

Sort a table with Rails (ajax way!)

Sorting of tables has been a trouble to many beginners as me. But I did stumble upon this which helped me kick start the issue ( Well it almost did the job for me!).

We are going to use a couple of helpers to do almost all of the work for us. You may use me why helpers? Well it is to reinforce the DRYness of Rails. We can use the methods inside the over multiple views.

So if you want the helpers to be used across all the controllers the best place to put them is under /app/helpers/application_helper.rb else you could put it in your controller specific helper.
Here goes the code and the explanation of the helpers that we will be using.

The first helper is nothing but necessary. It is called sort_td_class_helper, and his only goal is to add a class="sortup" if the column is currently the one used to sort the table, or a class="sortdown" if it is used to sort in reverse order. The only utility is to allow, with CSS, to indicates to the user which column is currently used as a sorting field.

The code is nothing interesting :

def sort_td_class_helper(param)
result = 'class="sortup"' if params[:sort] == param
result = 'class="sortdown"' if params[:sort] == param + " DESC"
return result
end

Then, we have a second helper, called sort_link_helper.
def sort_link_helper(text, param)
key = param
key += " DESC" if params[:sort] == param
options = {
:url => {:overwrite_params => {:sort => key, :page => nil}},
:update => 'table',
:before => "Element.show('spinner')",
:success => "Element.hide('spinner')"
}
html_options = {
:title => "Sort by this field",
:href => url_for(:action => 'list', :params => @params.merge({:sort => key, :page => nil}))
}
link_to_remote(text, options, html_options)
end

This helper takes two arguments :

  • text, which is just the text to be displayed as the column header and sort link
  • param, which is the name of the request parameter associated with the column.

The first two lines define a new variable, called key, which gets the value of the param argument, ie the sort key. The string DESC is concatenated to it if param is already the sorting key. This is used to implement sorting in both ascending and descending order. If the user select a sort link, it will sort by ascending order ; if it selects the same link again, it will sort by descending order, and so on.

The rest of the helper is to define the options for the final call to the link_to_remote function.

  • the options hash, used for the javascript Ajax link, contains the url to be called to generate the new HTML, the id of the element to update, and two before and success actions to show/hide the spinner image.
  • the html_options hash, used for the HTML link. It generates the content of the href attribute with the url_for Rails function.

Here is what the sort_link_helper returns for a call with the strings "Title" and "title" as text and param arguments :

<a href="/item/list?sort=qty" onclick="Element.show('spinner'); new Ajax.Updater('table', '/item/list?sort=qty', {asynchronous:true, evalScripts:true, onSuccess:function(request){Element.hide('spinner')}}); return false;" title="Sort by this field">Quantity</a>

The view would use the helper function like

<td <%= sort_td_class_helper "name" %>>
<%= sort_link_helper "Name", "name" %>
</td>
The controller would use the params[:sort] for obtaining the field to sort with and the manner in which it has to sort, i.e desc or asc.

Started Bloggin Finally!

Well, after getting inspired from a ton of blogs, websites and forums for a web application I developed with no knowledge of Ruby or Rails!, I decided it was finally time to go one step further, Write a blog. No body panics, I am not going to post the substandard code that I produce but the best code that I think is available for the topic from the net. Well you might find the spoilers in the form of my code in between so beware!