Location based searching in Rails 5 - Part 1 (using geokit-rails)

geosearch rails5

Why?

Location-based search can bring user experience value to modern websites and mobile apps. Such as allowing users to find nearby drivers/doctors/restaurants/lawyers/etc. This post will talk about adding geo-search ability to Rails API backend that enabled the location-based searching.

Business example

Let’s say we want to build a mobile app where users can find doctors nearby. So this is the core business requirement:

Users want to find doctors within 5 miles nearby

Technical specs

We will focus on Rails backend development in this post.

Available tools in Rails community

Apparently there are two common ways to go:

We will talk about Option 1 in this post.
Option 2 will be covered in Part 2 - using PostGIS extension.

Setup

Follow geokit-rails' github for first time setup. It's fairly easy to incorporate geokit into Rails project.

Model / Migration

Create model for doctor model like below.
Note the index created on clinic_latitude and clinic_longitude for performance purpose.

class CreateDoctors < ActiveRecord::Migration[5.1]  
  def change
    create_table :doctors do |t|
      t.decimal :clinic_latitude, precision: 10, scale: 6
      t.decimal :clinic_longitude, precision: 10, scale: 6
      t.string :clinic_name

      t.timestamps
    end
    add_index :doctors, [:clinic_latitude, :clinic_longitude]
  end
end  

Hook up GeoKit

Simply by adding acts_as_mappable in the model. lat_column_name and lng_column_name should match the latitude and longitude columns created in the table.

# models/doctor.rb
class Doctor < ApplicationRecord  
  acts_as_mappable :default_units => :miles,
                   :default_formula => :sphere,
                   :distance_field_name => :distance,
                   :lat_column_name => :clinic_latitude,
                   :lng_column_name => :clinic_longitude
end  

Quick Test

> rails console

# create some doctors record
> Doctor.create!(clinic_name: "Doc 1", clinic_latitude: 22.3129115, clinic_longitude: 114.2219923)
> Doctor.create!(clinic_name: "Doc 2", clinic_latitude: 22.3129125, clinic_longitude: 114.2219993)
> Doctor.create!(clinic_name: "Doc 3 (too far)", clinic_latitude: 22.9429125, clinic_longitude: 114.5019993)
> Doctor.within(5, :units => :miles, :origin => [22.3129115, 114.2219923])

# #<ActiveRecord::Relation [#<Doctor id: 9, clinic_latitude: 0.22312912e2, clinic_longitude: 0.114221992e3, clinic_name: "Doc 1", created_at: "2017-09-15 11:47:24", updated_at: "2017-09-15 11:47:24">, #<Doctor id: 10, clinic_latitude: 0.22312913e2, clinic_longitude: 0.114221999e3, clinic_name: "Doc 2", created_at: "2017-09-15 11:47:24", updated_at: "2017-09-15 11:47:24">]> 

Controller

Last step is to replace latitude, longitude and radius with the parameters sent from the app.

# controllers/doctors_controller.rb

def search  
  ... other stuff ...

  Doctor.within(
    params[:radius],
    :units => :miles,
    :origin => [
      params[:lat],
      params[:lng]
    ]
  )

  ... other stuff ...
end

Continue in Part 2 (using PostGIS extension)