Search code examples
sqlruby-on-railsrubyactiverecordrails-geocoder

Find all Obj who's Delivery Radius is within Geocoded Address


I wrote a function in my Rails app that has isn't passing a smell test and I'm unsure how to refactor.

Currently, a user is able to enter in an an address into a form and my goal is to spit out any delivery companies that are within a given delivery radius. Right now that looks like this:

class Dealers < ActiveRecord::Base
  validates :name, presence: true
  validates :delivery_radius, numericality: { only_integer: true }
end

so a search comes into my controller and I call a method I wrote (available_deliveries) like this:

  @dealers = Dealer.available_deliveries(Geocoder.coordinates(search_params))

search_params is just street, city & state.

my Dealer#available_deliveries method looks like this:

  def self.available_deliveries(geo)
    dealers = []
    Dealer.all.each do |dealer|
      if dealer.distance_from(geo) <= dealer.delivery_radius
        dealers << dealer
      end
    end
    dealers
  end

YIKES that's ugly. I'm unsure how to do a where SQL statement to get the same results...


Solution

  • Depending on your backing database, I suggest you use its abilities for indexing according to geo-spatial distance.

    Postgresql:

    Finding Records in a Radius

    Another great function provided by these extensions is the earthbox(lltoearth($latlngcube), $radiusinmetres) this function allows us to perform a simple compare to find all records in a certain radius. This is done by the function by returning the great circle distance between the points, a more thorough explanation is located at http://en.wikipedia.org/wiki/Greatcircle.

    This could be used to show all events in our current city.

    An example of such a query:

    SELECT events.id, events.name FROM events WHERE earth_box( {current_user_lat}, {current_user_lng}, {radius_in_metres}) @>
    

    ll_to_earth(events.lat, events.lng);

    MySql:

    We are using double to store latitude and longitude. In addition we precompute (by triggers) all values which are precomputable when looking at one point only. I currently don't have access to the formula we are using, will add this later. This is optimized for an optimal speed / precision balance.