Commit 5a19d5ada0044ada3ad8781546e81a99d595506c
Exists in
master
and in
21 other branches
Merge branch 'function_score' of https://github.com/monkbroc/searchkick into monkbroc-function_score
Showing
3 changed files
with
47 additions
and
0 deletions
Show diff stats
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 | ... | ... |