Skip to content

Commit

Permalink
Replace mongoid_search with ElasticSearch (via tire). Fixes #46
Browse files Browse the repository at this point in the history
The keywords field has a boost applied to it, so that search results where the search term matches one or more keywords (aka categories) will appear higher

Location searches are sorted by distance, as are nearby queries.
  • Loading branch information
monfresh committed Aug 16, 2013
1 parent b601fd2 commit 4cfb196
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 145 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ gem "attribute_normalizer"
gem "figaro"

# Search
gem 'mongoid_search', :git => "git://github.com/mauriciozaffari/mongoid_search.git"
gem "tire"
21 changes: 11 additions & 10 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@ GIT
specs:
geocoder (1.1.8)

GIT
remote: git://github.com/mauriciozaffari/mongoid_search.git
revision: cabd032afc4fccaa5d71d0ac065e66c72216b57a
specs:
mongoid_search (0.3.2)
fast-stemmer (~> 1.0.0)
mongoid (>= 3.0.0)

GIT
remote: git://github.com/monfresh/activeadmin-mongoid.git
revision: 99dcfaee9e222d912a4d469cb298aa61139aae63
Expand Down Expand Up @@ -66,6 +58,7 @@ GEM
activesupport (3.2.13)
i18n (= 0.6.1)
multi_json (~> 1.0)
ansi (1.4.3)
api-pagination (1.1.1)
api_smith (1.3.0)
hashie (>= 1.0, < 3.0)
Expand Down Expand Up @@ -142,7 +135,6 @@ GEM
factory_girl_rails (4.2.1)
factory_girl (~> 4.2.0)
railties (>= 3.0.0)
fast-stemmer (1.0.2)
fastercsv (1.5.5)
fattr (2.2.1)
figaro (0.7.0)
Expand All @@ -165,6 +157,7 @@ GEM
railties (>= 3.1, < 4.1)
has_scope (0.5.1)
hashie (1.2.0)
hashr (0.0.22)
hike (1.2.3)
hirb (0.7.1)
httparty (0.11.0)
Expand Down Expand Up @@ -344,6 +337,14 @@ GEM
tilt (~> 1.1, != 1.3.0)
thor (0.18.1)
tilt (1.4.1)
tire (0.6.0)
activemodel (>= 3.0)
activesupport
ansi
hashr (~> 0.0.19)
multi_json (~> 1.3)
rake
rest-client (~> 1.6)
treetop (1.4.14)
polyglot
polyglot (>= 0.3.1)
Expand Down Expand Up @@ -391,7 +392,6 @@ DEPENDENCIES
metric_fu
mongoid (>= 3.1.2)
mongoid-rspec (>= 1.7.0)
mongoid_search!
newrelic_rpm
quiet_assets (>= 1.0.2)
rack-cors
Expand All @@ -402,6 +402,7 @@ DEPENDENCIES
rocket_pants (~> 1.0)
rspec-rails (>= 2.12.2)
sass-rails (~> 3.2.3)
tire
uglifier (>= 1.0.3)
unicorn (>= 4.3.1)
validates_formatting_of
13 changes: 3 additions & 10 deletions app/controllers/api/v1/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,13 @@ def show

def nearby
org = Organization.find(params[:id])
@nearby = org.nearbys(current_radius).page(params[:page]).per(30)
@nearby = Organization.nearby(org, params)
expose @nearby
end

def search
@results = org_search(params).all.page(params[:page]).per(30)
begin
expose @results
rescue Moped::Errors::QueryFailure
error! :bad_request,
:metadata => {
:specific_reason => "Invalid ZIP code or address"
}
end
@results = Organization.search(params)
expose @results
end
end
end
Expand Down
41 changes: 0 additions & 41 deletions app/controllers/api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,4 @@ class ApiController < RocketPants::Base

map_error! Mongoid::Errors::DocumentNotFound, RocketPants::NotFound

CATEGORIES = %w[care education emergency food goods health housing legal money transit work]

private
def current_radius
if params[:radius].present?
begin
radius = Float params[:radius].to_s
# radius must be between 0.1 miles and 10 miles
[[0.1, radius].max, 10].min
rescue ArgumentError
error! :bad_request,
:metadata => { :specific_reason => "radius must be a number" }
end
else
5
end
end

def org_search(params)
organizations = scope_builder
error! :bad_request,
:metadata => {
:specific_reason => "keyword and location can't both be blank"
} if params[:keyword].blank? && params[:location].blank?

if params[:keyword] && CATEGORIES.include?(params[:keyword].strip.downcase)
organizations.find_by_category(params[:keyword])
elsif params[:keyword]
organizations.full_text_search(params[:keyword])
end
organizations.find_near(params[:location], current_radius) unless params[:location].blank?
organizations.find_by_language(params[:language]) if params[:language]
organizations.order_by(:name => :asc) if params[:sort] == "name"
organizations.order_by(:name => :asc) if params[:order] == "asc"
organizations.order_by(:name => :desc) if params[:order] == "desc"
organizations
end

def scope_builder
DynamicDelegator.new(Organization.scoped)
end
end
129 changes: 108 additions & 21 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,115 @@ class Organization
include RocketPants::Cacheable
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Search

search_in :name, :agency, :description, :keywords
include Tire::Model::Search
include Tire::Model::Callbacks

index_name "#{Rails.env.downcase}-#{Rails.application.class.to_s.downcase}-organizations"

def to_indexed_json
self.to_json
end

# def self.paginate(options = {})
# page(options[:page]).per(options[:per_page])
# end

mapping do
indexes :agency
indexes :name
indexes :description
indexes :keywords, boost: 5
indexes :coordinates, type: 'geo_point'
#indexes :hsa, boost: 10
end

def hsa
self.where(agency: "San Mateo County Human Services Agency")
end

#NE and SW geo coordinates that define the boundaries of San Mateo County
SMC_BOUNDS = [[37.1074,-122.521], [37.7084,-122.085]].freeze

def self.search(params={})
if params[:keyword].blank? && params[:location].blank? && params[:language].blank?
error_message("no_params")
end

# Google provides a "bounds" option to restrict the address search to
# a particular area. Since this app focuses on organizations in San Mateo
# County, we use SMC_BOUNDS to restrict the search.
result = Geocoder.search(params[:location], :bounds => SMC_BOUNDS)
coords = result.first.coordinates.reverse if result.present?

begin
tire.search(page: params[:page], per_page: 30) do
query do
match [:name, :agency, :description, :keywords], params[:keyword], type: 'phrase_prefix' if params[:keyword].present?
filtered do
filter :geo_distance, coordinates: coords, distance: "#{Organization.current_radius(params[:radius])}miles" if params[:location].present?
filter :term, :languages_spoken => params[:language].downcase if params[:language].present?
end
end
sort do
by :_geo_distance, :coordinates => coords, :unit => "mi", :order => "asc" if params[:location].present?
end
end
rescue Tire::Search::SearchRequestFailed
error_message("invalid_location")
end
end

def self.current_radius(radius)
if radius.present?
begin
radius = Float radius.to_s
# radius must be between 0.1 miles and 10 miles
[[0.1, radius].max, 10].min
rescue ArgumentError
error_message("invalid_radius")
end
else
5
end
end

def self.nearby(org, params={})
coords = org.coordinates
tire.search(page: params[:page], per_page: 30) do
query do
filtered do
filter :geo_distance, coordinates: coords, distance: "#{Organization.current_radius(params[:radius])}miles"
filter :not, { :ids => { :values => ["#{org.id}"] } }
end
end
sort do
by :_geo_distance, :coordinates => coords, :unit => "mi", :order => "asc"
end
end
end

#combines address fields together into one string
def address
"#{self.street_address}, #{self.city}, #{self.state} #{self.zipcode}"
end

def self.error_message(type)
error = RocketPants::BadRequest.new
if type == "no_params"
error.context =
{ metadata:
{ :specific_reason =>
"Either keyword, location, or language is missing." } }
elsif type == "invalid_location"
error.context =
{ metadata: { :specific_reason => "Invalid ZIP code or address." } }
elsif type == "invalid_radius"
error.context =
{ metadata: { :specific_reason => "radius must be a number." } }
end
raise error
end

normalize_attributes :agency, :city, :description, :eligibility_requirements,
:fees, :how_to_apply, :name, :service_hours, :service_wait,
Expand Down Expand Up @@ -67,23 +173,4 @@ class Organization
geocoded_by :address # can also be an IP address
#after_validation :geocode # auto-fetch coordinates

scope :find_by_category, lambda { |category| where(keywords: /#{category.strip}/i) }
scope :find_by_language, lambda { |language| where(languages_spoken: /#{language.strip}/i) }

#combines address fields together into one string
def address
"#{self.street_address}, #{self.city}, #{self.state} #{self.zipcode}"
end

#NE and SW geo coordinates that define the boundaries of San Mateo County
SMC_BOUNDS = [[37.1074,-122.521], [37.7084,-122.085]].freeze

# Google provides a "bounds" option to restrict the address search to
# a particular area. Since this app focues on organizations in San Mateo
# County, we use SMC_BOUNDS to restrict the search.
def self.find_near(location, radius)
result = Geocoder.search(location, :bounds => SMC_BOUNDS)
coords = result.first.coordinates if result.present?
near(coords, radius)
end
end
42 changes: 0 additions & 42 deletions config/initializers/mongoid_search.rb

This file was deleted.

0 comments on commit 4cfb196

Please sign in to comment.