Extending acts_as_locateable

There have been quite a few geographically-themed Rails plugins emerging over the past few months and I decided it was time to try out acts_as_locateable.

Acts_as_locateable is based on ZipCodeSearch. It loads in a database mapping US zip codes to coordinates and then adds convenience methods to ActiveRecord objects that let you search by distance. eg.

Event.find_within_radius(50, '49503')

will return all events within 50 miles of me.

What the standard plugin doesn’t allow is the passing in of more search parameters. So if I wanted to limit that search to future events I’d have to retrieve all the results and iterate over them. In a large system that could be very inefficient.

It turns out it’s not too hard to hack the plugin to make it accept other ActiveRecord parameters. By just adding a couple more checks to the parameter decoding, and adding an extra argument to the call to find. For my use it turned out to be easier to patch the original than re-open the class and add extra methods, though that would obviously be possible. For anyone who may want it, the patch is:

Index: lib/acts_as_locateable.rb
===================================================================
--- lib/acts_as_locateable.rb   (revision 29)
+++ lib/acts_as_locateable.rb   (working copy)
@@ -48,9 +48,13 @@
         # Find instances of the locatable class within radius_in_miles of your target.
         # Target can be specified in a number of ways, see expand_radius_args for options
         def find_within_radius(radius_in_miles, *args)
-          lat, lon = expand_radius_args(args)
+          standard_args = args.reject { |arg| arg.kind_of?(Hash) }
+          query_args = (args - standard_args).first
+
+          lat, lon = expand_radius_args(standard_args)
+          # query_args = args.find { |arg| arg.kind_of?(Hash) }
           within_radius_scope(radius_in_miles, lat, lon) do
-            find(:all)
+            find(:all, query_args)
           end
         end

Now I can use:

Event.find_within_radius(50, '49503',
	:conditions => ['starts_at > ?', DateTime.now],
	:order => 'starts_at')

and get much more useful results.

Tags: , , , ,

1 comment

  1. Thanks, this helped a lot!

    Another nice feature would be to get back the actual proximity distance for each object.

    The within_radius_scope_with_dist method doesn’t appear to even order the results from closest to furthest.

    Any ideas on how to patch in a new proximity field and order it?