Geokit and named_scope

Posted by Brian in Rails, tips (August 11th, 2009)

I discovered that the geokit_rails plugin doesn’t support named_scope. For those unfamiliar, named_scope methods can be chained to narrow down a query while only performing one query. Here’s an example:

class Business < ActiveRecord::Base
  acts_as_mappable 
  named_scope :active, :conditions => {:active => true}
  named_scope :nonprofit, :conditions => {:nonprofit => true}
end

@active_businesses = Business.active
@nonprofits = Business.nonprofit
@active_nonprofits = Business.active.nonprofit

Geokit has a really nice syntax for locating businesses within a certain distance.

@businesses = Business.find :all, :origin => "742 Evergreen Terrace, Springfield IL", :within => 10

However, you simply cannot, out of the box, use this with named_scopes. So I cannot do

@businesses = Business.active :origin => "742 Evergreen Terrace, Springfield IL", :within => 10

The way Geokit exposes itself to ActiveRecord is just not compatible.

RailsCasts to the Rescue!

Ryan Bates, Railscasts producer and all-around awesome guy, has posted a solution in which you can build your own dynamic named scopes. We can create our own scope for Geokit, because the Geokit-Rails plugin provides a public method to retrieve the distance SQL fragment.

First, we add a new named scope to our model

   named_scope :map_conditions, lambda { |*args| {:conditions => args} }

Next, we add our own method to handle the scopes.

    def self.by_location(options ={})
      scope = self.scoped({})
      scope = scope.map_conditions "#{distance_sql(options[:origin])} <= #{options[:within]}"
      scope
    end

We use this method to append our own conditions to the anonymous scope. we use the distance_sql method which takes whatever we pass in via our :origin option, and we concatonate the equality. In this case, we want distance less than or equal to our distance.

We can now do exactly what we wanted, although with modified syntax.

@businesses = Business.active.nonprofit.by_location :origin => "742 Evergreen Terrace, Springfield IL", :within => 10

Pretty amazing stuff.

2 Responses to ' Geokit and named_scope '

Subscribe to comments with RSS or TrackBack to ' Geokit and named_scope '.

  1. NickD said,
    on October 6th, 2009 at 8:22 am

    I found that I had to call MultiGeocoder.geocode(“742 Evergreen Terrace, Springfield IL”) first and pass that object to distance_sql. distance_sql doesn’t take in a straight address.

    Here is my named scope:
    named_scope :geocoded, lambda{ |origin, distance| distance_sql = self.distance_sql(MultiGeocoder.geocode(origin)); { :select => “businesses.*, #{distance_sql} as distance”, :conditions => “#{distance_sql} <= #{distance}" } }

  2. Brian said,
    on October 6th, 2009 at 8:30 pm

    @NickD:

    You’re right. I extracted this code from an existing example and forgot to handle the geocoding within the method. The self.by_location method does the geolocation lookup if a string is passed.

    I like your example. Very simple.

Leave a reply

:mrgreen: :neutral: :twisted: :shock: :smile: :???: :cool: :evil: :grin: :oops: :razz: :roll: :wink: :cry: :eek: :lol: :mad: :sad: