Monday, December 22, 2008
A Threesome in Chicago
Thursday, December 13, 2007
Calculate GeoCode using google-geocode
- 'rubygems'
- 'google_geocode'
gem install google-geocodedocumentation
Now when ever the user tries to save the address we can have a callback associated with it..
So in the users model we would define
before_save :calculateCoordinates
def calculateCoordinates
- catch(:done) do
- fullAddress = address+','+city+','+state+','+zip
- loc = Geocode.lookup(fullAddress)
- if loc
- puts "calculated geocode"
- self.lat = loc[:latitude]
- self.lon = loc[:longitude]
- else
- puts "Unable to calculate the geocode"
- end
- end
The hash that is returned is used to populate the user's model lat and lon methods before saving to the database.
If the code is unable to calculate the geo-code we can take necessary action in the else part.
The catch here is that I have refactored the code and use a Geocode class to do my ugly geo-code calculation. The file that contains my geocode class is /app/model/geocode.rb
- class Geocode < ActiveRecord::Base
- require 'rubygems'
- require 'google_geocode'
- def self.lookup(location)
- g = GoogleGeocode.new GOOGLE_GEO_API_KEY
- begin
- location = g.locate location
- {:latitude => location.latitude, :longitude => location.longitude}
- rescue GoogleGeocode::AddressError
- return false
- end
- end
- end
The lookup class method would return a hash containing the latitude and longitude when it is able to calculate the geo code or false when it encounters a problem while calculate the geo code for the bad address.
The GOOGLE_GEO_API_KEY is obtained by registering here
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.pngpublic/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!)
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, calledsort_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 twobefore
andsuccess
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 theurl_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" %>>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.
<%= sort_link_helper "Name", "name" %>
</td>