acts_as_geocodable (blog entry, repository) is the newest kid on the rails geo plugin block. It actually consists of two parts, a gem called graticule which handles the actual geocoding, interacting with external services, etc, and the plugin which offers extensions to your models.
I like that separation. Having the generalised code in a gem and the rails-specific hooks in a plugin makes a lot of sense and makes it much easier to use the core code in non-rails ruby apps, and having a single gem that supports multiple services allows for built-in failover should the preferred geocoder be unavailable.
Much of the functionality of the plugin is already integrated into my application, but not with quite so many options. In such cases I really enjoy installing plugins; there’s something very satisfying about going through my application deleting code.
The plugin adds two tables to your database. The first, geocodes, holds longitudes/latitudes for given addresses, while the other, geocodings, polymorphically links those geocodes to your existing models. In my case, this meant re-geocoding all the locations already in my database, but since I’m operating on a fairly small data set, that was a pretty simple case of iterating across them all and re-saving them. For those operating with very large databases, you may want to write a more sophisticated migration to handle that.
The trickiest thing was re-coding my search queries to use the new database. acts_as_geocodable offers a number of neat methods for running queries such as (from their documentation)
Event.find(:all, :within => 50, :origin => "97232")
Event.find(:nearest, :origin => "Portland, OR")
But I wanted a way to build more sophisticated searches so I could, say, limit by title and order by distance. It turns out that’s pretty easy too:
Location.find(:all, :origin => 'Grand Rapids MI',
:conditions => ['title LIKE ?', 'My Title'] :order => 'distance')
The one place where I had a problem was when trying to use the last of the examples.
Location.find(:nearest, :origin => 'Portland, OR')
blew up with:
ActiveRecord::StatementInvalid: Mysql::Error: Incorrect parameter count in the call to native function 'RADIANS': SELECT locations.*, geocodes.*,
(ACOS( SIN(RADIANS()) * SIN(RADIANS(geocodes.latitude)) + COS(RADIANS()) * COS(RADIANS(geocodes.latitude)) * COS(RADIANS(geocodes.longitude) - RADIANS()) ) * 3963.1676) AS
distance FROM locations JOIN geocodings ON
locations.id = geocodings.geocodable_id AND
geocodings.geocodable_type = 'Location'
JOIN geocodes ON geocodings.geocode_id = geocodes.id ORDER BY distance ASC LIMIT 1
but when I used a full address (my home) in the same query, I got an appropriate result. It looks as though perhaps if it fails to get an appropriate pair of co-ordinates for the specified location, it tries to perform the query anyway, with an exception resulting.
I also found some problems when trying to use the plugin with locations outside North America, but that is a limitation of the geocoding services and not of the gem or plugin themselves. Hasten the day when enough data is open that global geocoding services can become a reality.
Working with acts_as_geocodable has so far been a very straightforward experience and has allowed me to rid my code of some pieces I’d always meant to refactor out. It’d be good to see the error I ran into handled more neatly, and perhaps an obvious API to take advantage of the failover options presented by graticule, but the plugin is still early in its life and shows a lot of promise.