searchkick 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +8 -0
- data/README.md +38 -7
- data/gemfiles/nobrainer.gemfile +1 -1
- data/lib/searchkick.rb +1 -0
- data/lib/searchkick/index.rb +19 -3
- data/lib/searchkick/query.rb +65 -29
- data/lib/searchkick/results.rb +27 -15
- data/lib/searchkick/version.rb +1 -1
- data/test/boost_test.rb +11 -0
- data/test/facets_test.rb +1 -0
- data/test/match_test.rb +36 -0
- data/test/records_test.rb +10 -0
- data/test/sql_test.rb +40 -0
- data/test/test_helper.rb +8 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0227952cfe570c64d03eacd4db89689ec4a0b2d5
|
4
|
+
data.tar.gz: 14d2061a47667de91432894f8ef220b1a73bec3c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9e12c3156a856a53d7b5157f40b167dce9ff05949f5312f34100222b6769801ab602baba3ca10aec2e80db21e2ae0410229120d865f0682a7167827b4bb7b82
|
7
|
+
data.tar.gz: cbfbbaca6c443d5f319a97342d1969f85d94749b5662f86b3da557d310a9bd6ed02fc1d7af54e8f2ca0be703bf35c8bb49164bf4d13195e348c221708e8e9793
|
data/.travis.yml
CHANGED
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 "
|
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 "
|
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.
|
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
|
-
|
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 "
|
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 "
|
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 "
|
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.
|
data/gemfiles/nobrainer.gemfile
CHANGED
data/lib/searchkick.rb
CHANGED
data/lib/searchkick/index.rb
CHANGED
@@ -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
|
-
|
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",
|
data/lib/searchkick/query.rb
CHANGED
@@ -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
|
-
|
177
|
-
|
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:
|
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
|
data/lib/searchkick/results.rb
CHANGED
@@ -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
|
-
|
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"]]
|
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,
|
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 =>
|
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(
|
158
|
+
records.all.for_ids(ids)
|
147
159
|
elsif records.respond_to?(:queryable)
|
148
160
|
# Mongoid 3+
|
149
|
-
records.queryable.for_ids(
|
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 =>
|
164
|
+
records.unscoped.where(:id.in => ids)
|
153
165
|
else
|
154
166
|
raise "Not sure how to load records"
|
155
167
|
end
|
data/lib/searchkick/version.rb
CHANGED
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"}
|
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.
|
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-
|
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
|