searchkick 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 086a7f84d9047b486d127d05cc3d20b7b6d66b04
4
- data.tar.gz: f0896d8115143f72cf088f69f82c923c6e4f67cf
3
+ metadata.gz: 0227952cfe570c64d03eacd4db89689ec4a0b2d5
4
+ data.tar.gz: 14d2061a47667de91432894f8ef220b1a73bec3c
5
5
  SHA512:
6
- metadata.gz: f084aa390d924d64c610ea86845433bb521b8ad319e50350936f56d57f3db770e01e87c34c55d38f88b58599b03d6176a8af6e4fee8793fc5f1b8fb01cfeea6e
7
- data.tar.gz: e26a6809754329de5cf67a473f3df2d5b8b19b1981bdb22c041421b4916a1efcf5d4f78501cd9656ba297ccdcb0e3fe1742d0d57ffee1e0bf8475104045367fb
6
+ metadata.gz: d9e12c3156a856a53d7b5157f40b167dce9ff05949f5312f34100222b6769801ab602baba3ca10aec2e80db21e2ae0410229120d865f0682a7167827b4bb7b82
7
+ data.tar.gz: cbfbbaca6c443d5f319a97342d1969f85d94749b5662f86b3da557d310a9bd6ed02fc1d7af54e8f2ca0be703bf35c8bb49164bf4d13195e348c221708e8e9793
data/.travis.yml CHANGED
@@ -1,6 +1,5 @@
1
1
  language: ruby
2
- rvm:
3
- - 2.1
2
+ rvm: 2.2
4
3
  services:
5
4
  - elasticsearch
6
5
  - mongodb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.9.1
2
+
3
+ - `and` now matches `&`
4
+ - Added `transpositions` option to misspellings
5
+ - Added `boost_mode` and `log` options to `boost_by`
6
+ - Added `prefix_length` option to `misspellings`
7
+ - Added ability to set env
8
+
1
9
  ## 0.9.0
2
10
 
3
11
  - Much better performance for where queries if no facets
data/README.md CHANGED
@@ -64,7 +64,7 @@ Product.reindex
64
64
  And to query, use:
65
65
 
66
66
  ```ruby
67
- products = Product.search "2% Milk"
67
+ products = Product.search "apples"
68
68
  products.each do |product|
69
69
  puts product.name
70
70
  end
@@ -77,7 +77,7 @@ Searchkick supports the complete [Elasticsearch Search API](http://www.elasticse
77
77
  Query like SQL
78
78
 
79
79
  ```ruby
80
- Product.search "2% Milk", where: {in_stock: true}, limit: 10, offset: 50
80
+ Product.search "apples", where: {in_stock: true}, limit: 10, offset: 50
81
81
  ```
82
82
 
83
83
  Search specific fields
@@ -96,6 +96,7 @@ where: {
96
96
  store_id: {not: 2}, # not
97
97
  aisle_id: {not: [25, 30]}, # not in
98
98
  user_ids: {all: [1, 3]}, # all elements in array
99
+ category: /frozen .+/, # regexp
99
100
  or: [
100
101
  [{in_stock: true}, {backordered: true}]
101
102
  ]
@@ -267,18 +268,28 @@ end
267
268
 
268
269
  ### Misspellings
269
270
 
270
- By default, Searchkick handles misspelled queries by returning results with an [edit distance](http://en.wikipedia.org/wiki/Levenshtein_distance) of one. To turn off this feature, use:
271
+ By default, Searchkick handles misspelled queries by returning results with an [edit distance](http://en.wikipedia.org/wiki/Levenshtein_distance) of one.
272
+
273
+ You can change this with:
274
+
275
+ ```ruby
276
+ Product.search "zucini", misspellings: {edit_distance: 2} # zucchini
277
+ ```
278
+
279
+ Or turn off misspellings with:
271
280
 
272
281
  ```ruby
273
282
  Product.search "zuchini", misspellings: false # no zucchini
274
283
  ```
275
284
 
276
- You can also change the edit distance with:
285
+ Swapping two letters counts as two edits. To count the [transposition of two adjacent characters as a single edit](https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance), use:
277
286
 
278
287
  ```ruby
279
- Product.search "zucini", misspellings: {edit_distance: 2} # zucchini
288
+ Product.search "mikl", misspellings: {transpositions: true} # milk
280
289
  ```
281
290
 
291
+ This is planned to be the default in Searchkick 1.0.
292
+
282
293
  ### Indexing
283
294
 
284
295
  Control what data is indexed with the `search_data` method. Call `Product.reindex` after changing this method.
@@ -538,7 +549,7 @@ Product.search "wingtips", facets: {size: {where: {color: "brandy"}}}
538
549
  Limit
539
550
 
540
551
  ```ruby
541
- Product.search "2% Milk", facets: {store_id: {limit: 10}}
552
+ Product.search "apples", facets: {store_id: {limit: 10}}
542
553
  ```
543
554
 
544
555
  Ranges
@@ -849,7 +860,7 @@ To modify the query generated by Searchkick, use:
849
860
 
850
861
  ```ruby
851
862
  products =
852
- Product.search "2% Milk" do |body|
863
+ Product.search "apples" do |body|
853
864
  body[:query] = {match_all: {}}
854
865
  end
855
866
  ```
@@ -898,6 +909,14 @@ class Product < ActiveRecord::Base
898
909
  end
899
910
  ```
900
911
 
912
+ Use a dynamic index name
913
+
914
+ ```ruby
915
+ class Product < ActiveRecord::Base
916
+ searchkick index_name: -> { "#{name.tableize}-#{I18n.locale}" }
917
+ end
918
+ ```
919
+
901
920
  Prefix the index name
902
921
 
903
922
  ```ruby
@@ -990,6 +1009,18 @@ Reindex all models - Rails only
990
1009
  rake searchkick:reindex:all
991
1010
  ```
992
1011
 
1012
+ Turn on misspellings after a certain number of characters
1013
+
1014
+ ```ruby
1015
+ Product.search "api", misspellings: {prefix_length: 2} # api, apt, no ahi
1016
+ ```
1017
+
1018
+ **Note:** With this option, if the query length is the same as `prefix_length`, misspellings are turned off
1019
+
1020
+ ```ruby
1021
+ Product.search "ah", misspellings: {prefix_length: 2} # ah, no aha
1022
+ ```
1023
+
993
1024
  ## Large Data Sets
994
1025
 
995
1026
  For large data sets, check out [Keeping Elasticsearch in Sync](https://www.found.no/foundation/keeping-elasticsearch-in-sync/). Searchkick will make this easy in the future.
@@ -3,4 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in searchkick.gemspec
4
4
  gemspec path: "../"
5
5
 
6
- gem "nobrainer", "0.22.0"
6
+ gem "nobrainer", "0.27.0"
data/lib/searchkick.rb CHANGED
@@ -28,6 +28,7 @@ module Searchkick
28
28
  attr_accessor :wordnet_path
29
29
  attr_accessor :timeout
30
30
  attr_accessor :models
31
+ attr_writer :env
31
32
  end
32
33
  self.search_method_name = :search
33
34
  self.wordnet_path = "/var/lib/wn_s.pl"
@@ -130,7 +130,8 @@ module Searchkick
130
130
 
131
131
  # reindex
132
132
 
133
- def create_index
133
+ def create_index(options = {})
134
+ index_options = options[:index_options] || self.index_options
134
135
  index = Searchkick::Index.new("#{name}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}", @options)
135
136
  index.create(index_options)
136
137
  index
@@ -153,7 +154,7 @@ module Searchkick
153
154
 
154
155
  clean_indices
155
156
 
156
- index = create_index
157
+ index = create_index(index_options: scope.searchkick_index_options)
157
158
 
158
159
  # check if alias exists
159
160
  if alias_exists?
@@ -217,6 +218,9 @@ module Searchkick
217
218
  },
218
219
  default_index: {
219
220
  type: "custom",
221
+ # character filters -> tokenizer -> token filters
222
+ # https://www.elastic.co/guide/en/elasticsearch/guide/current/analysis-intro.html
223
+ char_filter: ["ampersand"],
220
224
  tokenizer: "standard",
221
225
  # synonym should come last, after stemming and shingle
222
226
  # shingle must come before searchkick_stemmer
@@ -224,11 +228,13 @@ module Searchkick
224
228
  },
225
229
  searchkick_search: {
226
230
  type: "custom",
231
+ char_filter: ["ampersand"],
227
232
  tokenizer: "standard",
228
233
  filter: ["standard", "lowercase", "asciifolding", "searchkick_search_shingle", "searchkick_stemmer"]
229
234
  },
230
235
  searchkick_search2: {
231
236
  type: "custom",
237
+ char_filter: ["ampersand"],
232
238
  tokenizer: "standard",
233
239
  filter: ["standard", "lowercase", "asciifolding", "searchkick_stemmer"]
234
240
  },
@@ -311,10 +317,20 @@ module Searchkick
311
317
  max_gram: 50
312
318
  },
313
319
  searchkick_stemmer: {
314
- type: "snowball",
320
+ # use stemmer if language is lowercase, snowball otherwise
321
+ # TODO deprecate language option in favor of stemmer
322
+ type: options[:language] == options[:language].to_s.downcase ? "stemmer" : "snowball",
315
323
  language: options[:language] || "English"
316
324
  }
317
325
  },
326
+ char_filter: {
327
+ # https://www.elastic.co/guide/en/elasticsearch/guide/current/custom-analyzers.html
328
+ # &_to_and
329
+ ampersand: {
330
+ type: "mapping",
331
+ mappings: ["&=> and "]
332
+ }
333
+ },
318
334
  tokenizer: {
319
335
  searchkick_autocomplete_ngram: {
320
336
  type: "edgeNGram",
@@ -15,9 +15,6 @@ module Searchkick
15
15
  @term = term
16
16
  @options = options
17
17
 
18
- below12 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.2.0")
19
- below14 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.4.0")
20
-
21
18
  boost_fields = {}
22
19
  fields =
23
20
  if options[:fields]
@@ -99,19 +96,21 @@ module Searchkick
99
96
  }
100
97
 
101
98
  if field == "_all" || field.end_with?(".analyzed")
102
- shared_options[:cutoff_frequency] = 0.001 unless operator == "and"
103
- qs.concat [
104
- shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search"),
105
- shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search2")
106
- ]
107
99
  misspellings = options.key?(:misspellings) ? options[:misspellings] : options[:mispellings] # why not?
108
100
  if misspellings != false
109
101
  edit_distance = (misspellings.is_a?(Hash) && (misspellings[:edit_distance] || misspellings[:distance])) || 1
102
+ transpositions = (misspellings.is_a?(Hash) && misspellings[:transpositions] == true) ? {fuzzy_transpositions: true} : {}
103
+ prefix_length = (misspellings.is_a?(Hash) && misspellings[:prefix_length]) || 0
110
104
  qs.concat [
111
- shared_options.merge(fuzziness: edit_distance, max_expansions: 3, analyzer: "searchkick_search"),
112
- shared_options.merge(fuzziness: edit_distance, max_expansions: 3, analyzer: "searchkick_search2")
105
+ shared_options.merge(fuzziness: edit_distance, prefix_length: prefix_length, max_expansions: 3, analyzer: "searchkick_search").merge(transpositions),
106
+ shared_options.merge(fuzziness: edit_distance, prefix_length: prefix_length, max_expansions: 3, analyzer: "searchkick_search2").merge(transpositions)
113
107
  ]
114
108
  end
109
+ shared_options[:cutoff_frequency] = 0.001 unless operator == "and" || misspellings == false
110
+ qs.concat [
111
+ shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search"),
112
+ shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search2")
113
+ ]
115
114
  elsif field.end_with?(".exact")
116
115
  f = field.split(".")[0..-2].join(".")
117
116
  queries << {match: {f => shared_options.merge(analyzer: "keyword")}}
@@ -133,7 +132,7 @@ module Searchkick
133
132
  if conversions_field && options[:conversions] != false
134
133
  # wrap payload in a bool query
135
134
  script_score =
136
- if below12
135
+ if below12?
137
136
  {script_score: {script: "doc['count'].value"}}
138
137
  else
139
138
  {field_value_factor: {field: "count"}}
@@ -164,31 +163,21 @@ module Searchkick
164
163
  end
165
164
 
166
165
  custom_filters = []
166
+ multiply_filters = []
167
167
 
168
168
  boost_by = options[:boost_by] || {}
169
+
169
170
  if boost_by.is_a?(Array)
170
171
  boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }]
172
+ elsif boost_by.is_a?(Hash)
173
+ multiply_by, boost_by = boost_by.partition { |k,v| v[:boost_mode] == "multiply" }.map{ |i| Hash[i] }
171
174
  end
172
175
  if options[:boost]
173
176
  boost_by[options[:boost]] = {factor: 1}
174
177
  end
175
178
 
176
- boost_by.each do |field, value|
177
- script_score =
178
- if below12
179
- {script_score: {script: "#{value[:factor].to_f} * log(doc['#{field}'].value + 2.718281828)"}}
180
- else
181
- {field_value_factor: {field: field, factor: value[:factor].to_f, modifier: "ln2p"}}
182
- end
183
-
184
- custom_filters << {
185
- filter: {
186
- exists: {
187
- field: field
188
- }
189
- }
190
- }.merge(script_score)
191
- end
179
+ custom_filters.concat boost_filters(boost_by, log: true)
180
+ multiply_filters.concat boost_filters(multiply_by || {})
192
181
 
193
182
  boost_where = options[:boost_where] || {}
194
183
  if options[:user_id] && personalize_field
@@ -237,6 +226,16 @@ module Searchkick
237
226
  }
238
227
  end
239
228
 
229
+ if multiply_filters.any?
230
+ payload = {
231
+ function_score: {
232
+ functions: multiply_filters,
233
+ query: payload,
234
+ score_mode: "multiply"
235
+ }
236
+ }
237
+ end
238
+
240
239
  payload = {
241
240
  query: payload,
242
241
  size: per_page,
@@ -294,7 +293,7 @@ module Searchkick
294
293
  payload[:facets][field] = {
295
294
  terms_stats: {
296
295
  key_field: field,
297
- value_script: below14 ? "doc.score" : "_score",
296
+ value_script: below14? ? "doc.score" : "_score",
298
297
  size: size
299
298
  }
300
299
  }
@@ -504,6 +503,8 @@ module Searchkick
504
503
  }
505
504
  }
506
505
  }
506
+ when :regexp # support for regexp queries without using a regexp ruby object
507
+ filters << {regexp: {field => {value: op_value}}}
507
508
  when :not # not equal
508
509
  filters << {not: term_filters(field, op_value)}
509
510
  when :all
@@ -558,10 +559,45 @@ module Searchkick
558
559
 
559
560
  def custom_filter(field, value, factor)
560
561
  {
561
- filter: term_filters(field, value),
562
+ filter: {
563
+ and: where_filters({field => value})
564
+ },
562
565
  boost_factor: factor
563
566
  }
564
567
  end
565
568
 
569
+ def boost_filters(boost_by, options = {})
570
+ boost_by.map do |field, value|
571
+ log = value.key?(:log) ? value[:log] : options[:log]
572
+ value[:factor] ||= 1
573
+ script_score =
574
+ if below12?
575
+ script = log ? "log(doc['#{field}'].value + 2.718281828)" : "doc['#{field}'].value"
576
+ {script_score: {script: "#{value[:factor].to_f} * #{script}"}}
577
+ else
578
+ {field_value_factor: {field: field, factor: value[:factor].to_f, modifier: log ? "ln2p" : nil}}
579
+ end
580
+
581
+ {
582
+ filter: {
583
+ exists: {
584
+ field: field
585
+ }
586
+ }
587
+ }.merge(script_score)
588
+ end
589
+ end
590
+
591
+ def below12?
592
+ below_version?("1.2.0")
593
+ end
594
+
595
+ def below14?
596
+ below_version?("1.4.0")
597
+ end
598
+
599
+ def below_version?(version)
600
+ Gem::Version.new(Searchkick.server_version) < Gem::Version.new(version)
601
+ end
566
602
  end
567
603
  end
@@ -15,6 +15,11 @@ module Searchkick
15
15
  @options = options
16
16
  end
17
17
 
18
+ # experimental: may not make next release
19
+ def records
20
+ @records ||= results_query(klass, hits)
21
+ end
22
+
18
23
  def results
19
24
  @results ||= begin
20
25
  if options[:load]
@@ -22,20 +27,12 @@ module Searchkick
22
27
  results = {}
23
28
 
24
29
  hits.group_by { |hit, i| hit["_type"] }.each do |type, grouped_hits|
25
- records = type.camelize.constantize
26
- if options[:includes]
27
- if defined?(NoBrainer::Document) && records < NoBrainer::Document
28
- records = records.preload(options[:includes])
29
- else
30
- records = records.includes(options[:includes])
31
- end
32
- end
33
- results[type] = results_query(records, grouped_hits)
30
+ results[type] = results_query(type.camelize.constantize, grouped_hits).to_a.index_by { |r| r.id.to_s }
34
31
  end
35
32
 
36
33
  # sort
37
34
  hits.map do |hit|
38
- results[hit["_type"]].find { |r| r.id.to_s == hit["_id"].to_s }
35
+ results[hit["_type"]][hit["_id"].to_s]
39
36
  end.compact
40
37
  else
41
38
  hits.map do |hit|
@@ -131,25 +128,40 @@ module Searchkick
131
128
  next_page.nil?
132
129
  end
133
130
 
131
+ def out_of_range?
132
+ current_page > total_pages
133
+ end
134
+
134
135
  def hits
135
136
  @response["hits"]["hits"]
136
137
  end
137
138
 
138
139
  private
139
140
 
140
- def results_query(records, grouped_hits)
141
+ def results_query(records, hits)
142
+ ids = hits.map { |hit| hit["_id"] }
143
+
144
+ if options[:includes]
145
+ records =
146
+ if defined?(NoBrainer::Document) && records < NoBrainer::Document
147
+ records.preload(options[:includes])
148
+ else
149
+ records.includes(options[:includes])
150
+ end
151
+ end
152
+
141
153
  if records.respond_to?(:primary_key) && records.primary_key
142
154
  # ActiveRecord
143
- records.where(records.primary_key => grouped_hits.map { |hit| hit["_id"] }).to_a
155
+ records.where(records.primary_key => ids)
144
156
  elsif records.respond_to?(:all) && records.all.respond_to?(:for_ids)
145
157
  # Mongoid 2
146
- records.all.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
158
+ records.all.for_ids(ids)
147
159
  elsif records.respond_to?(:queryable)
148
160
  # Mongoid 3+
149
- records.queryable.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
161
+ records.queryable.for_ids(ids)
150
162
  elsif records.respond_to?(:unscoped) && records.all.respond_to?(:preload)
151
163
  # Nobrainer
152
- records.unscoped.where(:id.in => grouped_hits.map { |hit| hit["_id"] }).to_a
164
+ records.unscoped.where(:id.in => ids)
153
165
  else
154
166
  raise "Not sure how to load records"
155
167
  end
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "0.9.0"
2
+ VERSION = "0.9.1"
3
3
  end
data/test/boost_test.rb CHANGED
@@ -101,6 +101,16 @@ class TestBoost < Minitest::Test
101
101
  assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost_by: {orders_count: {factor: 10}}
102
102
  end
103
103
 
104
+ def test_boost_by_boost_mode_multiply
105
+ store [
106
+ {name: "Tomato A", found_rate: 0.9},
107
+ {name: "Tomato B"},
108
+ {name: "Tomato C", found_rate: 0.5}
109
+ ]
110
+
111
+ assert_order "tomato", ["Tomato B", "Tomato A", "Tomato C"], boost_by: {found_rate: {boost_mode: "multiply"}}
112
+ end
113
+
104
114
  def test_boost_where
105
115
  store [
106
116
  {name: "Tomato A"},
@@ -108,6 +118,7 @@ class TestBoost < Minitest::Test
108
118
  {name: "Tomato C", user_ids: [3]}
109
119
  ]
110
120
  assert_first "tomato", "Tomato B", boost_where: {user_ids: 2}
121
+ assert_first "tomato", "Tomato B", boost_where: {user_ids: 1..2}
111
122
  assert_first "tomato", "Tomato B", boost_where: {user_ids: [1, 4]}
112
123
  assert_first "tomato", "Tomato B", boost_where: {user_ids: {value: 2, factor: 10}}
113
124
  assert_first "tomato", "Tomato B", boost_where: {user_ids: {value: [1, 4], factor: 10}}
data/test/facets_test.rb CHANGED
@@ -76,6 +76,7 @@ class TestFacets < Minitest::Test
76
76
  end
77
77
 
78
78
  def test_stats_facets
79
+ skip if Gem::Version.new(Searchkick.server_version) >= Gem::Version.new("1.4.0")
79
80
  options = {where: {store_id: 2}, facets: {store_id: {stats: true}}}
80
81
  facets = Product.search("Product", options).facets["store_id"]["terms"]
81
82
  expected_facets_keys = %w[term count total_count min max total mean]
data/test/match_test.rb CHANGED
@@ -112,6 +112,27 @@ class TestMatch < Minitest::Test
112
112
  assert_search "zip lock", ["Ziploc"]
113
113
  end
114
114
 
115
+ def test_misspelling_zucchini_transposition
116
+ store_names ["zucchini"]
117
+ assert_search "zuccihni", [] # doesn't work without transpositions:true option
118
+ assert_search "zuccihni", ["zucchini"], misspellings: {transpositions: true}
119
+ end
120
+
121
+ def test_misspelling_lasagna
122
+ store_names ["lasagna"]
123
+ assert_search "lasanga", ["lasagna"], misspellings: {transpositions: true}
124
+ assert_search "lasgana", ["lasagna"], misspellings: {transpositions: true}
125
+ assert_search "lasaang", [], misspellings: {transpositions: true} # triple transposition, shouldn't work
126
+ assert_search "lsagana", [], misspellings: {transpositions: true} # triple transposition, shouldn't work
127
+ end
128
+
129
+ def test_misspelling_lasagna_pasta
130
+ store_names ["lasagna pasta"]
131
+ assert_search "lasanga", ["lasagna pasta"], misspellings: {transpositions: true}
132
+ assert_search "lasanga pasta", ["lasagna pasta"], misspellings: {transpositions: true}
133
+ assert_search "lasanga pasat", ["lasagna pasta"], misspellings: {transpositions: true} # both words misspelled with a transposition should still work
134
+ end
135
+
115
136
  # spaces
116
137
 
117
138
  def test_spaces_in_field
@@ -153,6 +174,21 @@ class TestMatch < Minitest::Test
153
174
  assert_search "to be", ["to be or not to be"]
154
175
  end
155
176
 
177
+ def test_apostrophe
178
+ store_names ["Ben and Jerry's"]
179
+ assert_search "ben and jerrys", ["Ben and Jerry's"]
180
+ end
181
+
182
+ def test_ampersand_index
183
+ store_names ["Ben & Jerry's"]
184
+ assert_search "ben and jerrys", ["Ben & Jerry's"]
185
+ end
186
+
187
+ def test_ampersand_search
188
+ store_names ["Ben and Jerry's"]
189
+ assert_search "ben & jerrys", ["Ben and Jerry's"]
190
+ end
191
+
156
192
  def test_unsearchable
157
193
  store [
158
194
  {name: "Unsearchable", description: "Almond"}
@@ -0,0 +1,10 @@
1
+ require_relative "test_helper"
2
+
3
+ class RecordsTest < Minitest::Test
4
+
5
+ def test_records
6
+ store_names ["Milk", "Apple"]
7
+ assert_equal Product.search("milk").records.where(name: "Milk").count, 1
8
+ end
9
+
10
+ end
data/test/sql_test.rb CHANGED
@@ -40,6 +40,7 @@ class TestSql < Minitest::Test
40
40
  assert !products.first_page?
41
41
  assert !products.last_page?
42
42
  assert !products.empty?
43
+ assert !products.out_of_range?
43
44
  assert products.any?
44
45
  end
45
46
 
@@ -98,6 +99,11 @@ class TestSql < Minitest::Test
98
99
  assert_search "*", ["Product A"], where: {name: /Pro.+/}
99
100
  end
100
101
 
102
+ def test_alternate_regexp
103
+ store_names ["Product A", "Item B"]
104
+ assert_search "*", ["Product A"], where: {name: {regexp: "Pro.+"}}
105
+ end
106
+
101
107
  def test_where_string
102
108
  store [
103
109
  {name: "Product A", color: "RED"}
@@ -238,6 +244,40 @@ class TestSql < Minitest::Test
238
244
  assert_search "aaaa", ["aabb"], misspellings: {distance: 2}
239
245
  end
240
246
 
247
+ def test_misspellings_prefix_length
248
+ store_names ["ap", "api", "apt", "any", "nap", "ah", "ahi"]
249
+ assert_search "ap", ["ap"], misspellings: {prefix_length: 2}
250
+ assert_search "api", ["ap", "api", "apt"], misspellings: {prefix_length: 2}
251
+ end
252
+
253
+ def test_misspellings_prefix_length_operator
254
+ store_names ["ap", "api", "apt", "any", "nap", "ah", "aha"]
255
+ assert_search "ap ah", ["ap", "ah"], operator: "or", misspellings: {prefix_length: 2}
256
+ assert_search "api ahi", ["ap", "api", "apt", "ah", "aha"], operator: "or", misspellings: {prefix_length: 2}
257
+ end
258
+
259
+ def test_misspellings_fields_operator
260
+ store [
261
+ {name: "red", color: "red"},
262
+ {name: "blue", color: "blue"},
263
+ {name: "cyan", color: "blue green"},
264
+ {name: "magenta", color: "red blue"},
265
+ {name: "green", color: "green"}
266
+ ]
267
+ assert_search "red blue", ["red", "blue", "cyan", "magenta"], operator: "or", fields: ["color"], misspellings: false
268
+ end
269
+
270
+ def test_fields_operator
271
+ store [
272
+ {name: "red", color: "red"},
273
+ {name: "blue", color: "blue"},
274
+ {name: "cyan", color: "blue green"},
275
+ {name: "magenta", color: "red blue"},
276
+ {name: "green", color: "green"}
277
+ ]
278
+ assert_search "red blue", ["red", "blue", "cyan", "magenta"], operator: "or", fields: ["color"]
279
+ end
280
+
241
281
  def test_fields
242
282
  store [
243
283
  {name: "red", color: "light blue"},
data/test/test_helper.rb CHANGED
@@ -51,6 +51,7 @@ if defined?(Mongoid)
51
51
  field :in_stock, type: Boolean
52
52
  field :backordered, type: Boolean
53
53
  field :orders_count, type: Integer
54
+ field :found_rate, type: BigDecimal
54
55
  field :price, type: Integer
55
56
  field :color
56
57
  field :latitude, type: BigDecimal
@@ -85,27 +86,32 @@ elsif defined?(NoBrainer)
85
86
  include NoBrainer::Document
86
87
  include NoBrainer::Document::Timestamps
87
88
 
89
+ field :id, type: Object
88
90
  field :name, type: String
89
- field :store_id, type: Integer
90
91
  field :in_stock, type: Boolean
91
92
  field :backordered, type: Boolean
92
93
  field :orders_count, type: Integer
94
+ field :found_rate
93
95
  field :price, type: Integer
94
96
  field :color, type: String
95
97
  field :latitude
96
98
  field :longitude
97
99
  field :description, type: String
100
+
101
+ belongs_to :store, validates: false
98
102
  end
99
103
 
100
104
  class Store
101
105
  include NoBrainer::Document
102
106
 
107
+ field :id, type: Object
103
108
  field :name, type: String
104
109
  end
105
110
 
106
111
  class Animal
107
112
  include NoBrainer::Document
108
113
 
114
+ field :id, type: Object
109
115
  field :name, type: String
110
116
  end
111
117
 
@@ -135,6 +141,7 @@ else
135
141
  t.boolean :in_stock
136
142
  t.boolean :backordered
137
143
  t.integer :orders_count
144
+ t.decimal :found_rate
138
145
  t.integer :price
139
146
  t.string :color
140
147
  t.decimal :latitude, precision: 10, scale: 7
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchkick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-07 00:00:00.000000000 Z
11
+ date: 2015-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -137,6 +137,7 @@ files:
137
137
  - test/match_test.rb
138
138
  - test/model_test.rb
139
139
  - test/query_test.rb
140
+ - test/records_test.rb
140
141
  - test/reindex_job_test.rb
141
142
  - test/reindex_v2_job_test.rb
142
143
  - test/routing_test.rb
@@ -182,6 +183,7 @@ test_files:
182
183
  - test/match_test.rb
183
184
  - test/model_test.rb
184
185
  - test/query_test.rb
186
+ - test/records_test.rb
185
187
  - test/reindex_job_test.rb
186
188
  - test/reindex_v2_job_test.rb
187
189
  - test/routing_test.rb