Commit 31f846a08c8bb84a301c62217d6b12ff11dd6141
1 parent
2cf9d0fa
Exists in
master
and in
18 other branches
Added per-field misspellings
Co-authored-by: David Sweetman <david@davidsweetman.com>
Showing
5 changed files
with
74 additions
and
2 deletions
Show diff stats
CHANGELOG.md
README.md
... | ... | @@ -410,6 +410,14 @@ Turn off misspellings with: |
410 | 410 | Product.search "zuchini", misspellings: false # no zucchini |
411 | 411 | ``` |
412 | 412 | |
413 | +Specify which fields can include misspellings with: [master] | |
414 | + | |
415 | +```ruby | |
416 | +Product.search "zucini", fields: [:name, :color], misspellings: {fields: [:name]} | |
417 | +``` | |
418 | + | |
419 | +> When doing this, you must also specify fields to search | |
420 | + | |
413 | 421 | ### Bad Matches |
414 | 422 | |
415 | 423 | If a user searches `butter`, they may also get results for `peanut butter`. To prevent this, use: | ... | ... |
lib/searchkick/query.rb
... | ... | @@ -280,6 +280,15 @@ module Searchkick |
280 | 280 | prefix_length = (misspellings.is_a?(Hash) && misspellings[:prefix_length]) || 0 |
281 | 281 | default_max_expansions = @misspellings_below ? 20 : 3 |
282 | 282 | max_expansions = (misspellings.is_a?(Hash) && misspellings[:max_expansions]) || default_max_expansions |
283 | + misspellings_fields = misspellings.is_a?(Hash) && misspellings.key?(:fields) && misspellings[:fields].map { |f| "#{f}.#{@match_suffix}" } | |
284 | + | |
285 | + if misspellings_fields | |
286 | + missing_fields = misspellings_fields - fields | |
287 | + if missing_fields.any? | |
288 | + raise ArgumentError, "All fields in per-field misspellings must also be specified in fields option" | |
289 | + end | |
290 | + end | |
291 | + | |
283 | 292 | @misspellings = true |
284 | 293 | else |
285 | 294 | @misspellings = false |
... | ... | @@ -314,8 +323,10 @@ module Searchkick |
314 | 323 | exclude_analyzer = nil |
315 | 324 | exclude_field = field |
316 | 325 | |
326 | + field_misspellings = misspellings && (!misspellings_fields || misspellings_fields.include?(field)) | |
327 | + | |
317 | 328 | if field == "_all" || field.end_with?(".analyzed") |
318 | - shared_options[:cutoff_frequency] = 0.001 unless operator.to_s == "and" || misspellings == false | |
329 | + shared_options[:cutoff_frequency] = 0.001 unless operator.to_s == "and" || field_misspellings == false | |
319 | 330 | qs << shared_options.merge(analyzer: "searchkick_search") |
320 | 331 | |
321 | 332 | # searchkick_search and searchkick_search2 are the same for ukrainian |
... | ... | @@ -334,7 +345,7 @@ module Searchkick |
334 | 345 | exclude_analyzer = analyzer |
335 | 346 | end |
336 | 347 | |
337 | - if misspellings != false && match_type == :match | |
348 | + if field_misspellings != false && match_type == :match | |
338 | 349 | qs.concat(qs.map { |q| q.except(:cutoff_frequency).merge(fuzziness: edit_distance, prefix_length: prefix_length, max_expansions: max_expansions, boost: factor).merge(transpositions) }) |
339 | 350 | end |
340 | 351 | ... | ... |
test/misspellings_test.rb
... | ... | @@ -53,4 +53,48 @@ class MisspellingsTest < Minitest::Test |
53 | 53 | store_names ["abc", "abd", "aee"] |
54 | 54 | assert !Product.search("abc", misspellings: {below: 1}).misspellings? |
55 | 55 | end |
56 | + | |
57 | + def test_misspellings_field_correct_spelling_still_works | |
58 | + store [{name: "Sriracha", color: "blue"}] | |
59 | + assert_misspellings "Sriracha", ["Sriracha"], {fields: [:name, :color]} | |
60 | + assert_misspellings "blue", ["Sriracha"], {fields: [:name, :color]} | |
61 | + end | |
62 | + | |
63 | + def test_misspellings_field_enabled | |
64 | + store [{name: "Sriracha", color: "blue"}] | |
65 | + assert_misspellings "siracha", ["Sriracha"], {fields: [:name]} | |
66 | + assert_misspellings "clue", ["Sriracha"], {fields: [:color]} | |
67 | + end | |
68 | + | |
69 | + def test_misspellings_field_disabled | |
70 | + store [{name: "Sriracha", color: "blue"}] | |
71 | + assert_misspellings "siracha", [], {fields: [:color]} | |
72 | + assert_misspellings "clue", [], {fields: [:name]} | |
73 | + end | |
74 | + | |
75 | + def test_misspellings_field_with_transpositions | |
76 | + store [{name: "Sriracha", color: "blue"}] | |
77 | + assert_misspellings "lbue", [], {transpositions: false, fields: [:color]} | |
78 | + end | |
79 | + | |
80 | + def test_misspellings_field_with_edit_distance | |
81 | + store [{name: "Sriracha", color: "blue"}] | |
82 | + assert_misspellings "crue", ["Sriracha"], {edit_distance: 2, fields: [:color]} | |
83 | + end | |
84 | + | |
85 | + def test_misspellings_field_multiple | |
86 | + store [ | |
87 | + {name: "Greek Yogurt", color: "white"}, | |
88 | + {name: "Green Onions", color: "yellow"} | |
89 | + ] | |
90 | + assert_misspellings "greed", ["Greek Yogurt", "Green Onions"], {fields: [:name, :color]} | |
91 | + assert_misspellings "mellow", ["Green Onions"], {fields: [:name, :color]} | |
92 | + end | |
93 | + | |
94 | + def test_misspellings_field_requires_explicit_search_fields | |
95 | + store_names ["Sriracha"] | |
96 | + assert_raises(ArgumentError) do | |
97 | + assert_search "siracha", ["Sriracha"], {misspellings: {fields: [:name]}} | |
98 | + end | |
99 | + end | |
56 | 100 | end | ... | ... |
test/test_helper.rb
... | ... | @@ -579,6 +579,14 @@ class Minitest::Test |
579 | 579 | assert_equal expected, klass.search(term, options).map(&:name).first |
580 | 580 | end |
581 | 581 | |
582 | + def assert_misspellings(term, expected, misspellings = {}, klass = Product) | |
583 | + options = { | |
584 | + fields: [:name, :color], | |
585 | + misspellings: misspellings | |
586 | + } | |
587 | + assert_search(term, expected, options, klass) | |
588 | + end | |
589 | + | |
582 | 590 | def with_options(klass, options) |
583 | 591 | previous_options = klass.searchkick_options.dup |
584 | 592 | begin | ... | ... |