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 +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
|