The first step is to create an association between the User and its Location, so that the ActiveRecord for the Location can be referenced from that of the User.
class User < ActiveRecord::Base
belongs_to :location
attr_accessible :name
end
class Location < ActiveRecord::Base
attr_accessible :city, :latitude, :longitude, :zipcode
end
Next, use the association in your index.
You have to create an alias for a field on the Location model, to make sure the location table gets joined. And you must add attributes for the location's latitude and longitude:
ThinkingSphinx::Index.define :user, :with => :active_record do
# fields
indexes name
# attributes
has created_at, updated_at
has location.city, :as => :location_city
has "RADIANS(locations.latitude)", :as => :latitude, :type => :float
has "RADIANS(locations.longitude)", :as => :longitude, :type => :float
end
As others have already mentioned, since the earth is not flat, you'll need to account for that when you compute the distance between locations. The Haversine function is good for that. Thinking Sphinx has it built-in and you can filter and sort on it using :geo
.
Then for example, to find all users within 200 kilometers of lat / lng parameters in degrees, ordered by nearest first:
class DistanceController < ApplicationController
def search
@lat = params[:lat].to_f * Math::PI / 180
@lng = params[:lng].to_f * Math::PI / 180
@users = User.search :geo => [@lat, @lng], :with => {:geodist => 0.0..200_000.0}, :order => "geodist ASC"
end
end
For debugging, it's nice to know that in the view you can refer to the computed distance too:
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.location.zipcode %></td>
<td><%= user.location.city %></td>
<td><%= user.distance %></td>
</tr>
EDIT: added more detail about my working version, for completeness' sake also adding the table definitions. (MySQL, generated using db:migrate, these are the create scripts as MySQL Workbench generates them):
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`location_id` int(11) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `index_users_on_location_id` (`location_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `locations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`zipcode` varchar(255) DEFAULT NULL,
`latitude` float DEFAULT NULL,
`longitude` float DEFAULT NULL,
`city` varchar(255) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;