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,6 +410,14 @@ Turn off misspellings with: | ||
410 | Product.search "zuchini", misspellings: false # no zucchini | 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 | ### Bad Matches | 421 | ### Bad Matches |
414 | 422 | ||
415 | If a user searches `butter`, they may also get results for `peanut butter`. To prevent this, use: | 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,6 +280,15 @@ module Searchkick | ||
280 | prefix_length = (misspellings.is_a?(Hash) && misspellings[:prefix_length]) || 0 | 280 | prefix_length = (misspellings.is_a?(Hash) && misspellings[:prefix_length]) || 0 |
281 | default_max_expansions = @misspellings_below ? 20 : 3 | 281 | default_max_expansions = @misspellings_below ? 20 : 3 |
282 | max_expansions = (misspellings.is_a?(Hash) && misspellings[:max_expansions]) || default_max_expansions | 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 | @misspellings = true | 292 | @misspellings = true |
284 | else | 293 | else |
285 | @misspellings = false | 294 | @misspellings = false |
@@ -314,8 +323,10 @@ module Searchkick | @@ -314,8 +323,10 @@ module Searchkick | ||
314 | exclude_analyzer = nil | 323 | exclude_analyzer = nil |
315 | exclude_field = field | 324 | exclude_field = field |
316 | 325 | ||
326 | + field_misspellings = misspellings && (!misspellings_fields || misspellings_fields.include?(field)) | ||
327 | + | ||
317 | if field == "_all" || field.end_with?(".analyzed") | 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 | qs << shared_options.merge(analyzer: "searchkick_search") | 330 | qs << shared_options.merge(analyzer: "searchkick_search") |
320 | 331 | ||
321 | # searchkick_search and searchkick_search2 are the same for ukrainian | 332 | # searchkick_search and searchkick_search2 are the same for ukrainian |
@@ -334,7 +345,7 @@ module Searchkick | @@ -334,7 +345,7 @@ module Searchkick | ||
334 | exclude_analyzer = analyzer | 345 | exclude_analyzer = analyzer |
335 | end | 346 | end |
336 | 347 | ||
337 | - if misspellings != false && match_type == :match | 348 | + if field_misspellings != false && match_type == :match |
338 | 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) }) | 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 | end | 350 | end |
340 | 351 |
test/misspellings_test.rb
@@ -53,4 +53,48 @@ class MisspellingsTest < Minitest::Test | @@ -53,4 +53,48 @@ class MisspellingsTest < Minitest::Test | ||
53 | store_names ["abc", "abd", "aee"] | 53 | store_names ["abc", "abd", "aee"] |
54 | assert !Product.search("abc", misspellings: {below: 1}).misspellings? | 54 | assert !Product.search("abc", misspellings: {below: 1}).misspellings? |
55 | end | 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 | end | 100 | end |
test/test_helper.rb
@@ -579,6 +579,14 @@ class Minitest::Test | @@ -579,6 +579,14 @@ class Minitest::Test | ||
579 | assert_equal expected, klass.search(term, options).map(&:name).first | 579 | assert_equal expected, klass.search(term, options).map(&:name).first |
580 | end | 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 | def with_options(klass, options) | 590 | def with_options(klass, options) |
583 | previous_options = klass.searchkick_options.dup | 591 | previous_options = klass.searchkick_options.dup |
584 | begin | 592 | begin |