Commit 31f846a08c8bb84a301c62217d6b12ff11dd6141

Authored by Andrew
1 parent 2cf9d0fa

Added per-field misspellings

Co-authored-by: David Sweetman <david@davidsweetman.com>
CHANGELOG.md
1 1 ## 3.1.1 [unreleased]
2 2  
  3 +- Added per-field misspellings
3 4 - Made `exclude` option work with match all
4 5  
5 6 ## 3.1.0
... ...
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 &lt; 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
... ...