0

I currently have a model setup like so (-> = one-to-many):

Trader->Service->ServiceLocation<-Locations->Trader (if that makes sense)

A trader has an origin location and a service has a range, each time a new service is saved I request the new service object call an instance method .save_in_range! which takes the trader_location, loops through all locations in the database and determines the distance between the two, if it is within the range of the service it saves a relationship in the ServiceLocations table:

class Service < ActiveRecord::Base
    belongs_to :trader
    has_many :service_locations
    has_many :locations, through: :service_locations

    def save_in_range!
        trader_origin = Trader.find(self.trader_id).location
      locations = Location.all

      locations.each do |location|
          if location.distance_from(trader_origin) <= self.range
            self.locations << location
          end
      end
    end
end

My question is, do I really need to do this in this manner or am I missing some OML magic? If not is this an efficient way or am I writing awful code?

JonleePeakman
  • 649
  • 1
  • 5
  • 11

1 Answers1

0

So using your estimated numbers, we can see that every time a trader changes location, you'll have to make a loop across all locations, an operation on the order of 1000's. I think it's worthwhile to prepare for performance issues now, since this basically means we have a 1000-fold multiplier on the cost of trader.location-related operations, and it won't be all that difficult to do so.

I'm going to make a couple assumptions:

  • Each location is defined by two coordinates, x/y, long+lat/spherical angles, whatever
  • Distance requires some computation, e.g. hypotenuse in 2D or haversine on a globe

I would not load all the locations into Ruby, just to save a handful of location_ids back into a record. Instead, you should index every location by both coordinates and compute the distance in a SQL query.

The SQL query alone will make a big difference, but should you need to optimize further:

  • return only the location_ids from the SQL query to be saved in ruby
  • or don't return the location_ids at all and just update the service directly in the query
  • if your world is a globe, then store coordinates as spherical angles to save computation
  • before running the distance computation on all locations in the query, first filter by a bounding rectangle (rectangle of coordinates if world is 2D, or rectangle of spherical angles if on a globe)

I haven't really gone into specific implementation details here, so let me know if you have trouble finding resources about the different components I've mentioned thus far.

Community
  • 1
  • 1
Kache
  • 15,647
  • 12
  • 51
  • 79
  • As :autosave sets up a :before_save callback I would still need to identify all locations within range of the service though right? – JonleePeakman May 22 '14 at 09:39
  • Oh, shoot. After re-reading your question a bit, I realize I misunderstood your question, and I'm going to have to completely re-write my answer. Before I do so though, tell me, what's the general order of magnitude of traders, services, and locations? Now and in the future? – Kache May 22 '14 at 10:20
  • This is a new service I am developing and if you mean number of rows, location has around 3000 records and will not change much over time, Traders is effectively users so could go into the 1000's and services is one to many from traders so if I had a thousand users with 10 services I would have 10,000 rows. – JonleePeakman May 22 '14 at 10:36
  • Actually I wish my users would be 1000s but hey you get the idea :) – JonleePeakman May 22 '14 at 12:17