Commit 5a19d5ada0044ada3ad8781546e81a99d595506c

Authored by Andrew Kane
2 parents 49940253 79bcd5a8

Merge branch 'function_score' of https://github.com/monkbroc/searchkick into monkbroc-function_score

README.md
... ... @@ -616,6 +616,17 @@ Bounded by a box
616 616 City.search "san", where: {location: {top_left: [38, -123], bottom_right: [37, -122]}}
617 617 ```
618 618  
  619 +Modify search score by proximity to a point:
  620 +
  621 +```ruby
  622 +City.search "san", proximity_factor: { field: :location, origin: [37, -122] } # field and origin mandatory
  623 +City.search "san", proximity_factor: { field: :location, origin: [37, -122], function: :linear, scale: "30mi", decay: 0.5 } # at 30mi, decrease score by 50%
  624 +```
  625 +
  626 +The difference between where with a geo_point and proximity_factor is that the former is a hard cutoff that simply includes or excludes results but has no impact on the order of results, whereas the latter smoothly modifies the search score so that the results with the best match for the query text and the distance are returned.
  627 +
  628 +See [more details on modifying the score by proximity](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_decay_functions).
  629 +
619 630 ## Inheritance
620 631  
621 632 Searchkick supports single table inheritance.
... ...
lib/searchkick/query.rb
... ... @@ -219,6 +219,33 @@ module Searchkick
219 219 }
220 220 end
221 221  
  222 + # Modify the query scoring by proximity to a geo point
  223 + if options[:proximity_factor]
  224 + proximity_factor = { function: :gauss, score_mode: :multiply, scale: "5mi" }.merge(options[:proximity_factor])
  225 + (proximity_factor[:field] and proximity_factor[:origin]) or raise ArgumentError, "query() option :proximity_factor must contain at least :field and :origin"
  226 +
  227 + field = proximity_factor[:field]
  228 + function = proximity_factor[:function]
  229 + function_params = proximity_factor.select { |k,v| [:origin, :scale, :offset, :decay].include? k }
  230 + # Conform to GeoJSON convention of [longitude, latitude]
  231 + function_params[:origin] = function_params[:origin].reverse
  232 + score_mode = proximity_factor[:score_mode]
  233 +
  234 + payload = {
  235 + function_score: {
  236 + functions: [
  237 + {
  238 + function => {
  239 + field => function_params
  240 + }
  241 + }
  242 + ],
  243 + query: payload,
  244 + score_mode: score_mode
  245 + }
  246 + }
  247 + end
  248 +
222 249 payload = {
223 250 query: payload,
224 251 size: per_page,
... ...
test/sql_test.rb
... ... @@ -261,6 +261,15 @@ class TestSql < Minitest::Test
261 261 assert_search "product", ["Product"], where: {latitude: {gt: 99}}
262 262 end
263 263  
  264 + def test_proximity
  265 + store [
  266 + {name: "San Francisco", latitude: 37.7833, longitude: -122.4167},
  267 + {name: "San Antonio", latitude: 29.4167, longitude: -98.5000},
  268 + {name: "San Marino", latitude: 43.9333, longitude: 12.4667}
  269 + ]
  270 + assert_order "san", ["San Francisco", "San Antonio", "San Marino"], proximity_factor: {field: :location, origin: [37, -122]}
  271 + end
  272 +
264 273 # load
265 274  
266 275 def test_load_default
... ...