searchkick 0.3.2 → 0.4.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/.gitignore +1 -1
- data/.travis.yml +5 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +0 -2
- data/README.md +79 -28
- data/gemfiles/mongoid3.gemfile +6 -0
- data/gemfiles/mongoid4.gemfile +6 -0
- data/lib/searchkick/model.rb +36 -2
- data/lib/searchkick/reindex.rb +5 -1
- data/lib/searchkick/results.rb +10 -0
- data/lib/searchkick/search.rb +50 -9
- data/lib/searchkick/similar.rb +1 -1
- data/lib/searchkick/tasks.rb +2 -0
- data/lib/searchkick/version.rb +1 -1
- data/searchkick.gemspec +1 -1
- data/test/facets_test.rb +14 -3
- data/test/highlight_test.rb +22 -0
- data/test/inheritance_test.rb +24 -0
- data/test/sql_test.rb +45 -7
- data/test/suggest_test.rb +3 -3
- data/test/test_helper.rb +19 -6
- metadata +10 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 84aa6414f91ce1c76d0c247e9a11dfce4417c2ee
|
|
4
|
+
data.tar.gz: f072c08de08bac3808112ad165199b98b5b2e507
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b866b6ba7f26bca01b13043c42ae1e9917b664be9bf77ce0c269dde30298fc8d2a08a54ce247dd465e500ebe8e122e804685ab4855984c1a09088b46422900e7
|
|
7
|
+
data.tar.gz: ab9bae0de9a80123cb85e0a34f221b6dce56efc91471c3037fbce10b2c7e284635291704965c1894c419e3b7610da8e1ffa4d83cde4a76039a530ef0b26e7407
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
|
@@ -4,6 +4,7 @@ rvm:
|
|
|
4
4
|
- 2.0.0
|
|
5
5
|
services:
|
|
6
6
|
- elasticsearch
|
|
7
|
+
- mongodb
|
|
7
8
|
script: bundle exec rake test
|
|
8
9
|
before_script:
|
|
9
10
|
- psql -c 'create database searchkick_test;' -U postgres
|
|
@@ -11,3 +12,7 @@ notifications:
|
|
|
11
12
|
email:
|
|
12
13
|
on_success: never
|
|
13
14
|
on_failure: change
|
|
15
|
+
gemfile:
|
|
16
|
+
- Gemfile
|
|
17
|
+
- gemfiles/mongoid3.gemfile
|
|
18
|
+
- gemfiles/mongoid4.gemfile
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## 0.4.1
|
|
2
|
+
|
|
3
|
+
- Fixed issue w/ inheritance mapping
|
|
4
|
+
|
|
5
|
+
## 0.4.0
|
|
6
|
+
|
|
7
|
+
- Added support for Mongoid 4
|
|
8
|
+
- Added support for multiple locations
|
|
9
|
+
|
|
10
|
+
## 0.3.5
|
|
11
|
+
|
|
12
|
+
- Added facet ranges
|
|
13
|
+
- Added all operator
|
|
14
|
+
|
|
15
|
+
## 0.3.4
|
|
16
|
+
|
|
17
|
+
- Added highlighting
|
|
18
|
+
- Added :distance option to misspellings
|
|
19
|
+
- Fixed issue w/ BigDecimal serialization
|
|
20
|
+
|
|
21
|
+
## 0.3.3
|
|
22
|
+
|
|
23
|
+
- Better error messages
|
|
24
|
+
- Added where: {field: nil} queries
|
|
25
|
+
|
|
1
26
|
## 0.3.2
|
|
2
27
|
|
|
3
28
|
- Added support for single table inheritance
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Plus:
|
|
|
29
29
|
|
|
30
30
|
## Get Started
|
|
31
31
|
|
|
32
|
-
[Install Elasticsearch](http://www.elasticsearch.org/guide/reference/setup
|
|
32
|
+
[Install Elasticsearch](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup.html). For Homebrew, use:
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
35
|
brew install elasticsearch
|
|
@@ -87,6 +87,7 @@ where: {
|
|
|
87
87
|
aisle_id: [25, 30], # in
|
|
88
88
|
store_id: {not: 2}, # not
|
|
89
89
|
aisle_id: {not: [25, 30]}, # not in
|
|
90
|
+
user_ids: {all: [1, 3]}, # all elements in array
|
|
90
91
|
or: [
|
|
91
92
|
[{in_stock: true}, {backordered: true}]
|
|
92
93
|
]
|
|
@@ -111,6 +112,14 @@ Boost by a field
|
|
|
111
112
|
boost: "orders_count" # give popular documents a little boost
|
|
112
113
|
```
|
|
113
114
|
|
|
115
|
+
### Get Everything
|
|
116
|
+
|
|
117
|
+
Use a `*` for the query.
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
Product.search "*"
|
|
121
|
+
```
|
|
122
|
+
|
|
114
123
|
### Pagination
|
|
115
124
|
|
|
116
125
|
Plays nicely with kaminari and will_paginate.
|
|
@@ -152,7 +161,13 @@ Call `Product.reindex` after changing synonyms.
|
|
|
152
161
|
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:
|
|
153
162
|
|
|
154
163
|
```ruby
|
|
155
|
-
Product.search "zuchini", misspellings: false
|
|
164
|
+
Product.search "zuchini", misspellings: false # no zucchini
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
You can also change the edit distance with:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
Product.search "zucini", misspellings: {distance: 2} # zucchini
|
|
156
171
|
```
|
|
157
172
|
|
|
158
173
|
### Indexing
|
|
@@ -342,6 +357,37 @@ Advanced
|
|
|
342
357
|
Product.search "2% Milk", facets: {store_id: {where: {in_stock: true}, limit: 10}}
|
|
343
358
|
```
|
|
344
359
|
|
|
360
|
+
Ranges
|
|
361
|
+
|
|
362
|
+
```ruby
|
|
363
|
+
price_ranges = [{to: 20}, {from: 20, to: 50}, {from: 50}]
|
|
364
|
+
Product.search "*", facets: {price: {ranges: price_ranges}}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Highlight
|
|
368
|
+
|
|
369
|
+
Highlight the search query in the results.
|
|
370
|
+
|
|
371
|
+
```ruby
|
|
372
|
+
bands = Band.search "cinema", fields: [:name], highlight: true
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Note:** The `fields` option is required.
|
|
376
|
+
|
|
377
|
+
View the highlighted fields with:
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
bands.with_details.each do |band, details|
|
|
381
|
+
puts details[:highlight][:name] # "Two Door <em>Cinema</em> Club"
|
|
382
|
+
end
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
To change the tag, use:
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
Band.search "cinema", fields: [:name], highlight: {tag: "<strong>"}
|
|
389
|
+
```
|
|
390
|
+
|
|
345
391
|
### Similar Items
|
|
346
392
|
|
|
347
393
|
Find similar items.
|
|
@@ -360,7 +406,7 @@ class City < ActiveRecord::Base
|
|
|
360
406
|
searchkick locations: ["location"]
|
|
361
407
|
|
|
362
408
|
def search_data
|
|
363
|
-
|
|
409
|
+
attributes.merge location: [latitude, longitude]
|
|
364
410
|
end
|
|
365
411
|
end
|
|
366
412
|
```
|
|
@@ -377,6 +423,35 @@ Bounded by a box
|
|
|
377
423
|
City.search "san", where: {location: {top_left: [38, -123], bottom_right: [37, -122]}}
|
|
378
424
|
```
|
|
379
425
|
|
|
426
|
+
## Inheritance
|
|
427
|
+
|
|
428
|
+
Searchkick supports single table inheritance.
|
|
429
|
+
|
|
430
|
+
```ruby
|
|
431
|
+
class Dog < Animal
|
|
432
|
+
end
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
The parent and child model can both reindex.
|
|
436
|
+
|
|
437
|
+
```ruby
|
|
438
|
+
Animal.reindex
|
|
439
|
+
Dog.reindex # equivalent
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
And to search, use:
|
|
443
|
+
|
|
444
|
+
```ruby
|
|
445
|
+
Animal.search "*" # all animals
|
|
446
|
+
Dog.search "*" # just dogs
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Note:** The `suggest` option retrieves suggestions from the parent at the moment.
|
|
450
|
+
|
|
451
|
+
```ruby
|
|
452
|
+
Dog.search "airbudd", suggest: true # suggestions for all animals
|
|
453
|
+
```
|
|
454
|
+
|
|
380
455
|
## Deployment
|
|
381
456
|
|
|
382
457
|
Searchkick uses `ENV["ELASTICSEARCH_URL"]` for the Elasticsearch server. This defaults to `http://localhost:9200`.
|
|
@@ -419,29 +494,6 @@ Then deploy and reindex:
|
|
|
419
494
|
rake searchkick:reindex CLASS=Product
|
|
420
495
|
```
|
|
421
496
|
|
|
422
|
-
## Inheritance
|
|
423
|
-
|
|
424
|
-
Searchkick supports single table inheritance.
|
|
425
|
-
|
|
426
|
-
```ruby
|
|
427
|
-
class Dog < Animal
|
|
428
|
-
end
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
The parent and child model can both reindex.
|
|
432
|
-
|
|
433
|
-
```ruby
|
|
434
|
-
Animal.reindex
|
|
435
|
-
Dog.reindex # equivalent
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
And to search, use:
|
|
439
|
-
|
|
440
|
-
```ruby
|
|
441
|
-
Animal.search "*" # all animals
|
|
442
|
-
Dog.search "*" # just dogs
|
|
443
|
-
```
|
|
444
|
-
|
|
445
497
|
## Reference
|
|
446
498
|
|
|
447
499
|
Searchkick requires Elasticsearch `0.90.0` or higher.
|
|
@@ -528,7 +580,7 @@ rake searchkick:reindex:all
|
|
|
528
580
|
|
|
529
581
|
```ruby
|
|
530
582
|
class Product < ActiveRecord::Base
|
|
531
|
-
|
|
583
|
+
searchkick
|
|
532
584
|
end
|
|
533
585
|
```
|
|
534
586
|
|
|
@@ -560,7 +612,6 @@ Thanks to Karel Minarik for [Tire](https://github.com/karmi/tire), Jaroslav Kali
|
|
|
560
612
|
|
|
561
613
|
## TODO
|
|
562
614
|
|
|
563
|
-
- Analytics for searches and conversions
|
|
564
615
|
- Generate autocomplete predictions from past search queries
|
|
565
616
|
- Automatic failover
|
|
566
617
|
- Make Searchkick work with any language
|
data/lib/searchkick/model.rb
CHANGED
|
@@ -43,6 +43,9 @@ module Searchkick
|
|
|
43
43
|
# stringify fields
|
|
44
44
|
source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo}
|
|
45
45
|
|
|
46
|
+
# Mongoid 4 hack
|
|
47
|
+
source["_id"] = source["_id"].to_s if source["_id"]
|
|
48
|
+
|
|
46
49
|
options = self.class.searchkick_options
|
|
47
50
|
|
|
48
51
|
# conversions
|
|
@@ -53,14 +56,45 @@ module Searchkick
|
|
|
53
56
|
|
|
54
57
|
# hack to prevent generator field doesn't exist error
|
|
55
58
|
(options[:suggest] || []).map(&:to_s).each do |field|
|
|
56
|
-
source[field] =
|
|
59
|
+
source[field] = nil if !source[field]
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
# locations
|
|
60
63
|
(options[:locations] || []).map(&:to_s).each do |field|
|
|
61
|
-
|
|
64
|
+
if source[field]
|
|
65
|
+
if source[field].first.is_a?(Array) # array of arrays
|
|
66
|
+
source[field] = source[field].map{|a| a.map(&:to_f).reverse }
|
|
67
|
+
else
|
|
68
|
+
source[field] = source[field].map(&:to_f).reverse
|
|
69
|
+
end
|
|
70
|
+
end
|
|
62
71
|
end
|
|
63
72
|
|
|
73
|
+
# change all BigDecimal values to floats due to
|
|
74
|
+
# https://github.com/rails/rails/issues/6033
|
|
75
|
+
# possible loss of precision :/
|
|
76
|
+
cast_big_decimal =
|
|
77
|
+
proc do |obj|
|
|
78
|
+
case obj
|
|
79
|
+
when BigDecimal
|
|
80
|
+
obj.to_f
|
|
81
|
+
when Hash
|
|
82
|
+
obj.each do |k, v|
|
|
83
|
+
obj[k] = cast_big_decimal.call(v)
|
|
84
|
+
end
|
|
85
|
+
when Enumerable
|
|
86
|
+
obj.map! do |v|
|
|
87
|
+
cast_big_decimal.call(v)
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
obj
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
cast_big_decimal.call(source)
|
|
95
|
+
|
|
96
|
+
# p search_data
|
|
97
|
+
|
|
64
98
|
source.to_json
|
|
65
99
|
end
|
|
66
100
|
|
data/lib/searchkick/reindex.rb
CHANGED
|
@@ -35,6 +35,8 @@ module Searchkick
|
|
|
35
35
|
searchkick_import(index) # import after swap
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
index.refresh
|
|
39
|
+
|
|
38
40
|
true
|
|
39
41
|
end
|
|
40
42
|
|
|
@@ -203,6 +205,8 @@ module Searchkick
|
|
|
203
205
|
fields: {
|
|
204
206
|
field => {type: "string", index: "not_analyzed"},
|
|
205
207
|
"analyzed" => {type: "string", index: "analyzed"}
|
|
208
|
+
# term_vector: "with_positions_offsets" for fast / correct highlighting
|
|
209
|
+
# http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html#_fast_vector_highlighter
|
|
206
210
|
}
|
|
207
211
|
}
|
|
208
212
|
if autocomplete.include?(field)
|
|
@@ -221,7 +225,7 @@ module Searchkick
|
|
|
221
225
|
end
|
|
222
226
|
|
|
223
227
|
mappings = {
|
|
224
|
-
|
|
228
|
+
_default_: {
|
|
225
229
|
properties: mapping,
|
|
226
230
|
# https://gist.github.com/kimchy/2898285
|
|
227
231
|
dynamic_templates: [
|
data/lib/searchkick/results.rb
CHANGED
|
@@ -9,6 +9,16 @@ module Searchkick
|
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def with_details
|
|
13
|
+
each_with_hit.map do |model, hit|
|
|
14
|
+
details = {}
|
|
15
|
+
if hit["highlight"]
|
|
16
|
+
details[:highlight] = Hash[ hit["highlight"].map{|k, v| [k.sub(/\.analyzed\z/, "").to_sym, v.first] } ]
|
|
17
|
+
end
|
|
18
|
+
[model, details]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
12
22
|
# fixes deprecation warning
|
|
13
23
|
def __find_records_by_ids(klass, ids)
|
|
14
24
|
@options[:load] === true ? klass.find(ids) : klass.includes(@options[:load][:include]).find(ids)
|
data/lib/searchkick/search.rb
CHANGED
|
@@ -70,9 +70,10 @@ module Searchkick
|
|
|
70
70
|
{multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")}
|
|
71
71
|
]
|
|
72
72
|
if options[:misspellings] != false
|
|
73
|
+
distance = (options[:misspellings].is_a?(Hash) && options[:misspellings][:distance]) || 1
|
|
73
74
|
queries.concat [
|
|
74
|
-
{multi_match: shared_options.merge(fuzziness:
|
|
75
|
-
{multi_match: shared_options.merge(fuzziness:
|
|
75
|
+
{multi_match: shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search")},
|
|
76
|
+
{multi_match: shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search2")}
|
|
76
77
|
]
|
|
77
78
|
end
|
|
78
79
|
payload = {
|
|
@@ -160,6 +161,8 @@ module Searchkick
|
|
|
160
161
|
proc do |where|
|
|
161
162
|
filters = []
|
|
162
163
|
(where || {}).each do |field, value|
|
|
164
|
+
field = :_id if field.to_s == "id"
|
|
165
|
+
|
|
163
166
|
if field == :or
|
|
164
167
|
value.each do |or_clause|
|
|
165
168
|
filters << {or: or_clause.map{|or_statement| {and: where_filters.call(or_statement)} }}
|
|
@@ -200,6 +203,8 @@ module Searchkick
|
|
|
200
203
|
else
|
|
201
204
|
filters << {not: {term: {field => op_value}}}
|
|
202
205
|
end
|
|
206
|
+
elsif op == :all
|
|
207
|
+
filters << {terms: {field => op_value, execution: "and"}}
|
|
203
208
|
else
|
|
204
209
|
range_query =
|
|
205
210
|
case op
|
|
@@ -218,7 +223,11 @@ module Searchkick
|
|
|
218
223
|
end
|
|
219
224
|
end
|
|
220
225
|
else
|
|
221
|
-
|
|
226
|
+
if value.nil?
|
|
227
|
+
filters << {missing: {"field" => field, existence: true, null_value: true}}
|
|
228
|
+
else
|
|
229
|
+
filters << {term: {field => value}}
|
|
230
|
+
end
|
|
222
231
|
end
|
|
223
232
|
end
|
|
224
233
|
end
|
|
@@ -245,12 +254,22 @@ module Searchkick
|
|
|
245
254
|
facets.each do |field, facet_options|
|
|
246
255
|
# ask for extra facets due to
|
|
247
256
|
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
257
|
+
|
|
258
|
+
if facet_options[:ranges]
|
|
259
|
+
payload[:facets][field] = {
|
|
260
|
+
range: {
|
|
261
|
+
field.to_sym => facet_options[:ranges]
|
|
262
|
+
}
|
|
252
263
|
}
|
|
253
|
-
|
|
264
|
+
else
|
|
265
|
+
payload[:facets][field] = {
|
|
266
|
+
terms: {
|
|
267
|
+
field: field,
|
|
268
|
+
size: facet_options[:limit] ? facet_options[:limit] + 150 : 100000
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
end
|
|
272
|
+
|
|
254
273
|
facet_limits[field] = facet_options[:limit] if facet_options[:limit]
|
|
255
274
|
|
|
256
275
|
# offset is not possible
|
|
@@ -284,6 +303,17 @@ module Searchkick
|
|
|
284
303
|
end
|
|
285
304
|
end
|
|
286
305
|
|
|
306
|
+
# highlight
|
|
307
|
+
if options[:highlight]
|
|
308
|
+
payload[:highlight] = {
|
|
309
|
+
fields: Hash[ fields.map{|f| [f, {}] } ]
|
|
310
|
+
}
|
|
311
|
+
if options[:highlight].is_a?(Hash) and tag = options[:highlight][:tag]
|
|
312
|
+
payload[:highlight][:pre_tags] = [tag]
|
|
313
|
+
payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
287
317
|
# An empty array will cause only the _id and _type for each hit to be returned
|
|
288
318
|
# http://www.elasticsearch.org/guide/reference/api/search/fields/
|
|
289
319
|
payload[:fields] = [] if load
|
|
@@ -291,7 +321,18 @@ module Searchkick
|
|
|
291
321
|
tire_options = {load: load, payload: payload, size: per_page, from: offset}
|
|
292
322
|
tire_options[:type] = document_type if self != searchkick_klass
|
|
293
323
|
search = Tire::Search::Search.new(index_name, tire_options)
|
|
294
|
-
|
|
324
|
+
begin
|
|
325
|
+
response = search.json
|
|
326
|
+
rescue Tire::Search::SearchRequestFailed => e
|
|
327
|
+
status_code = e.message[0..3].to_i
|
|
328
|
+
if status_code == 404
|
|
329
|
+
raise "Index missing - run #{searchkick_klass.name}.reindex"
|
|
330
|
+
elsif status_code == 500 and (e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") or e.message.include?("No query registered for [multi_match]"))
|
|
331
|
+
raise "Upgrade Elasticsearch to 0.90.0 or greater"
|
|
332
|
+
else
|
|
333
|
+
raise e
|
|
334
|
+
end
|
|
335
|
+
end
|
|
295
336
|
|
|
296
337
|
# apply facet limit in client due to
|
|
297
338
|
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
data/lib/searchkick/similar.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Searchkick
|
|
|
9
9
|
# TODO deep merge method
|
|
10
10
|
options[:where] ||= {}
|
|
11
11
|
options[:where][:_id] ||= {}
|
|
12
|
-
options[:where][:_id][:not] = id
|
|
12
|
+
options[:where][:_id][:not] = id.to_s
|
|
13
13
|
options[:limit] ||= 10
|
|
14
14
|
options[:similar] = true
|
|
15
15
|
self.class.search(like_text, options)
|
data/lib/searchkick/tasks.rb
CHANGED
|
@@ -23,8 +23,10 @@ namespace :searchkick do
|
|
|
23
23
|
task :all => :environment do
|
|
24
24
|
Rails.application.eager_load!
|
|
25
25
|
(Searchkick::Reindex.instance_variable_get(:@descendents) || []).each do |model|
|
|
26
|
+
puts "Reindexing #{model.name}..."
|
|
26
27
|
model.reindex
|
|
27
28
|
end
|
|
29
|
+
puts "Reindex complete"
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
|
data/lib/searchkick/version.rb
CHANGED
data/searchkick.gemspec
CHANGED
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
|
25
25
|
spec.add_development_dependency "rake"
|
|
26
|
-
spec.add_development_dependency "minitest"
|
|
26
|
+
spec.add_development_dependency "minitest", "~> 4.7"
|
|
27
27
|
spec.add_development_dependency "activerecord"
|
|
28
28
|
spec.add_development_dependency "pg"
|
|
29
29
|
end
|
data/test/facets_test.rb
CHANGED
|
@@ -5,9 +5,9 @@ class TestFacets < Minitest::Unit::TestCase
|
|
|
5
5
|
def setup
|
|
6
6
|
super
|
|
7
7
|
store [
|
|
8
|
-
{name: "Product Show", store_id: 1, in_stock: true, color: "blue"},
|
|
9
|
-
{name: "Product Hide", store_id: 2, in_stock: false, color: "green"},
|
|
10
|
-
{name: "Product B", store_id: 2, in_stock: false, color: "red"}
|
|
8
|
+
{name: "Product Show", store_id: 1, in_stock: true, color: "blue", price: 21},
|
|
9
|
+
{name: "Product Hide", store_id: 2, in_stock: false, color: "green", price: 25},
|
|
10
|
+
{name: "Product B", store_id: 2, in_stock: false, color: "red", price: 5}
|
|
11
11
|
]
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -26,4 +26,15 @@ class TestFacets < Minitest::Unit::TestCase
|
|
|
26
26
|
assert_equal 1, facet["other"]
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
def test_ranges
|
|
30
|
+
price_ranges = [{to: 10}, {from: 10, to: 20}, {from: 20}]
|
|
31
|
+
facet = Product.search("Product", facets: {price: {ranges: price_ranges}}).facets["price"]
|
|
32
|
+
|
|
33
|
+
assert_equal 3, facet["ranges"].size
|
|
34
|
+
assert_equal 10.0, facet["ranges"][0]["to"]
|
|
35
|
+
assert_equal 20.0, facet["ranges"][2]["from"]
|
|
36
|
+
assert_equal 1, facet["ranges"][0]["count"]
|
|
37
|
+
assert_equal 0, facet["ranges"][1]["count"]
|
|
38
|
+
assert_equal 2, facet["ranges"][2]["count"]
|
|
39
|
+
end
|
|
29
40
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class TestHighlight < Minitest::Unit::TestCase
|
|
4
|
+
|
|
5
|
+
def test_basic
|
|
6
|
+
store_names ["Two Door Cinema Club"]
|
|
7
|
+
assert_equal "Two Door <em>Cinema</em> Club", Product.search("cinema", fields: [:name], highlight: true).with_details.first[1][:highlight][:name]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_tag
|
|
11
|
+
store_names ["Two Door Cinema Club"]
|
|
12
|
+
assert_equal "Two Door <strong>Cinema</strong> Club", Product.search("cinema", fields: [:name], highlight: {tag: "<strong>"}).with_details.first[1][:highlight][:name]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_multiple_fields
|
|
16
|
+
store [{name: "Two Door Cinema Club", color: "Cinema Orange"}]
|
|
17
|
+
highlight = Product.search("cinema", fields: [:name, :color], highlight: true).with_details.first[1][:highlight]
|
|
18
|
+
assert_equal "Two Door <em>Cinema</em> Club", highlight[:name]
|
|
19
|
+
assert_equal "<em>Cinema</em> Orange", highlight[:color]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
data/test/inheritance_test.rb
CHANGED
|
@@ -30,4 +30,28 @@ class TestInheritance < Minitest::Unit::TestCase
|
|
|
30
30
|
assert_equal 2, Animal.search("bear").size
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def test_child_autocomplete
|
|
34
|
+
store_names ["Max"], Cat
|
|
35
|
+
store_names ["Mark"], Dog
|
|
36
|
+
assert_equal ["Max"], Cat.search("ma", fields: [:name], autocomplete: true).map(&:name)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_parent_autocomplete
|
|
40
|
+
store_names ["Max"], Cat
|
|
41
|
+
store_names ["Bear"], Dog
|
|
42
|
+
assert_equal ["Bear"], Animal.search("bea", fields: [:name], autocomplete: true).map(&:name).sort
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# def test_child_suggest
|
|
46
|
+
# store_names ["Shark"], Cat
|
|
47
|
+
# store_names ["Sharp"], Dog
|
|
48
|
+
# assert_equal ["shark"], Cat.search("shar", fields: [:name], suggest: true).suggestions
|
|
49
|
+
# end
|
|
50
|
+
|
|
51
|
+
def test_parent_suggest
|
|
52
|
+
store_names ["Shark"], Cat
|
|
53
|
+
store_names ["Tiger"], Dog
|
|
54
|
+
assert_equal ["tiger"], Animal.search("tige", fields: [:name], suggest: true).suggestions.sort
|
|
55
|
+
end
|
|
56
|
+
|
|
33
57
|
end
|
data/test/sql_test.rb
CHANGED
|
@@ -39,7 +39,7 @@ class TestSql < Minitest::Unit::TestCase
|
|
|
39
39
|
now = Time.now
|
|
40
40
|
store [
|
|
41
41
|
{name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4, user_ids: [1, 2, 3]},
|
|
42
|
-
{name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3},
|
|
42
|
+
{name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3, user_ids: [1]},
|
|
43
43
|
{name: "Product C", store_id: 3, in_stock: false, backordered: true, created_at: now - 2, orders_count: 2},
|
|
44
44
|
{name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1},
|
|
45
45
|
]
|
|
@@ -64,15 +64,30 @@ class TestSql < Minitest::Unit::TestCase
|
|
|
64
64
|
assert_search "product", ["Product A", "Product B", "Product C"], where: {or: [[{in_stock: true}, {store_id: 3}]]}
|
|
65
65
|
assert_search "product", ["Product A", "Product B", "Product C"], where: {or: [[{orders_count: [2, 4]}, {store_id: [1, 2]}]]}
|
|
66
66
|
assert_search "product", ["Product A", "Product D"], where: {or: [[{orders_count: 1}, {created_at: {gte: now - 1}, backordered: true}]]}
|
|
67
|
-
#
|
|
68
|
-
assert_search "product", ["Product A"], where: {user_ids:
|
|
67
|
+
# all
|
|
68
|
+
assert_search "product", ["Product A"], where: {user_ids: {all: [1, 3]}}
|
|
69
|
+
assert_search "product", [], where: {user_ids: {all: [1, 2, 3, 4]}}
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def test_where_string
|
|
72
73
|
store [
|
|
73
74
|
{name: "Product A", color: "RED"}
|
|
74
75
|
]
|
|
75
|
-
assert_search "product", ["Product A"], where: {color:
|
|
76
|
+
assert_search "product", ["Product A"], where: {color: "RED"}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_where_nil
|
|
80
|
+
store [
|
|
81
|
+
{name: "Product A"},
|
|
82
|
+
{name: "Product B", color: "red"}
|
|
83
|
+
]
|
|
84
|
+
assert_search "product", ["Product A"], where: {color: nil}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_where_id
|
|
88
|
+
store_names ["Product A"]
|
|
89
|
+
product = Product.last
|
|
90
|
+
assert_search "product", ["Product A"], where: {id: product.id.to_s}
|
|
76
91
|
end
|
|
77
92
|
|
|
78
93
|
def test_near
|
|
@@ -100,6 +115,14 @@ class TestSql < Minitest::Unit::TestCase
|
|
|
100
115
|
assert_search "san", ["San Francisco"], where: {location: {top_left: [38, -123], bottom_right: [37, -122]}}
|
|
101
116
|
end
|
|
102
117
|
|
|
118
|
+
def test_multiple_locations
|
|
119
|
+
store [
|
|
120
|
+
{name: "San Francisco", latitude: 37.7833, longitude: -122.4167},
|
|
121
|
+
{name: "San Antonio", latitude: 29.4167, longitude: -98.5000}
|
|
122
|
+
]
|
|
123
|
+
assert_search "san", ["San Francisco"], where: {multiple_locations: {near: [37.5, -122.5]}}
|
|
124
|
+
end
|
|
125
|
+
|
|
103
126
|
def test_order_hash
|
|
104
127
|
store_names ["Product A", "Product B", "Product C", "Product D"]
|
|
105
128
|
assert_order "product", ["Product D", "Product C", "Product B", "Product A"], order: {name: :desc}
|
|
@@ -121,6 +144,11 @@ class TestSql < Minitest::Unit::TestCase
|
|
|
121
144
|
assert_search "abc", ["abc"], misspellings: false
|
|
122
145
|
end
|
|
123
146
|
|
|
147
|
+
def test_misspellings_distance
|
|
148
|
+
store_names ["abbb", "aabb"]
|
|
149
|
+
assert_search "aaaa", ["aabb"], misspellings: {distance: 2}
|
|
150
|
+
end
|
|
151
|
+
|
|
124
152
|
def test_fields
|
|
125
153
|
store [
|
|
126
154
|
{name: "red", color: "light blue"},
|
|
@@ -142,6 +170,13 @@ class TestSql < Minitest::Unit::TestCase
|
|
|
142
170
|
assert_first "blue", "Blue B", fields: [:name, :color]
|
|
143
171
|
end
|
|
144
172
|
|
|
173
|
+
def test_big_decimal
|
|
174
|
+
store [
|
|
175
|
+
{name: "Product", latitude: 100.0}
|
|
176
|
+
]
|
|
177
|
+
assert_search "product", ["Product"], where: {latitude: {gt: 99}}
|
|
178
|
+
end
|
|
179
|
+
|
|
145
180
|
# load
|
|
146
181
|
|
|
147
182
|
def test_load_default
|
|
@@ -159,9 +194,12 @@ class TestSql < Minitest::Unit::TestCase
|
|
|
159
194
|
assert_kind_of Tire::Results::Item, Product.search("product", load: false, include: [:store]).first
|
|
160
195
|
end
|
|
161
196
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
# TODO see if Mongoid is loaded
|
|
198
|
+
if !defined?(Mongoid)
|
|
199
|
+
def test_include
|
|
200
|
+
store_names ["Product A"]
|
|
201
|
+
assert Product.search("product", include: [:store]).first.association(:store).loaded?
|
|
202
|
+
end
|
|
165
203
|
end
|
|
166
204
|
|
|
167
205
|
end
|
data/test/suggest_test.rb
CHANGED
|
@@ -4,17 +4,17 @@ class TestSuggest < Minitest::Unit::TestCase
|
|
|
4
4
|
|
|
5
5
|
def test_basic
|
|
6
6
|
store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
|
|
7
|
-
assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark"
|
|
7
|
+
assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [:name]
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def test_perfect
|
|
11
11
|
store_names ["Tiger Shark", "Great White Shark"]
|
|
12
|
-
assert_suggest "Tiger Shark", nil # no correction
|
|
12
|
+
assert_suggest "Tiger Shark", nil, fields: [:name] # no correction
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def test_phrase
|
|
16
16
|
store_names ["Big Tiger Shark", "Tiger Sharp Teeth", "Tiger Sharp Mind"]
|
|
17
|
-
assert_suggest "How to catch a big tiger shar", "how to catch a big tiger shark"
|
|
17
|
+
assert_suggest "How to catch a big tiger shar", "how to catch a big tiger shark", fields: [:name]
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def test_without_option
|
data/test/test_helper.rb
CHANGED
|
@@ -11,14 +11,24 @@ Tire.configure do
|
|
|
11
11
|
pretty true
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
if
|
|
14
|
+
if defined?(Mongoid)
|
|
15
15
|
Mongoid.configure do |config|
|
|
16
16
|
config.connect_to "searchkick_test"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
class Product
|
|
20
20
|
include Mongoid::Document
|
|
21
|
-
|
|
21
|
+
include Mongoid::Timestamps
|
|
22
|
+
|
|
23
|
+
field :name
|
|
24
|
+
field :store_id, type: Integer
|
|
25
|
+
field :in_stock, type: Boolean
|
|
26
|
+
field :backordered, type: Boolean
|
|
27
|
+
field :orders_count, type: Integer
|
|
28
|
+
field :price, type: Integer
|
|
29
|
+
field :color
|
|
30
|
+
field :latitude, type: BigDecimal
|
|
31
|
+
field :longitude, type: BigDecimal
|
|
22
32
|
end
|
|
23
33
|
|
|
24
34
|
class Store
|
|
@@ -27,6 +37,8 @@ if ENV["MONGOID"]
|
|
|
27
37
|
|
|
28
38
|
class Animal
|
|
29
39
|
include Mongoid::Document
|
|
40
|
+
|
|
41
|
+
field :name
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
class Dog < Animal
|
|
@@ -53,6 +65,7 @@ else
|
|
|
53
65
|
t.boolean :in_stock
|
|
54
66
|
t.boolean :backordered
|
|
55
67
|
t.integer :orders_count
|
|
68
|
+
t.integer :price
|
|
56
69
|
t.string :color
|
|
57
70
|
t.decimal :latitude, precision: 10, scale: 7
|
|
58
71
|
t.decimal :longitude, precision: 10, scale: 7
|
|
@@ -99,17 +112,17 @@ class Product
|
|
|
99
112
|
suggest: [:name, :color],
|
|
100
113
|
conversions: "conversions",
|
|
101
114
|
personalize: "user_ids",
|
|
102
|
-
locations: ["location"]
|
|
115
|
+
locations: ["location", "multiple_locations"]
|
|
103
116
|
|
|
104
117
|
attr_accessor :conversions, :user_ids
|
|
105
118
|
|
|
106
119
|
def search_data
|
|
107
|
-
|
|
120
|
+
serializable_hash.merge conversions: conversions, user_ids: user_ids, location: [latitude, longitude], multiple_locations: [[latitude, longitude], [0, 0]]
|
|
108
121
|
end
|
|
109
122
|
end
|
|
110
123
|
|
|
111
124
|
class Animal
|
|
112
|
-
searchkick
|
|
125
|
+
searchkick autocomplete: [:name], suggest: [:name]
|
|
113
126
|
end
|
|
114
127
|
|
|
115
128
|
Product.searchkick_index.delete if Product.searchkick_index.exists?
|
|
@@ -118,7 +131,7 @@ Product.reindex # run twice for both index paths
|
|
|
118
131
|
|
|
119
132
|
Animal.reindex
|
|
120
133
|
|
|
121
|
-
class
|
|
134
|
+
class Minitest::Unit::TestCase
|
|
122
135
|
|
|
123
136
|
def setup
|
|
124
137
|
Product.destroy_all
|
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.
|
|
4
|
+
version: 0.4.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: 2013-
|
|
11
|
+
date: 2013-12-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tire
|
|
@@ -70,16 +70,16 @@ dependencies:
|
|
|
70
70
|
name: minitest
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
|
-
- -
|
|
73
|
+
- - ~>
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
75
|
+
version: '4.7'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
|
-
- -
|
|
80
|
+
- - ~>
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
82
|
+
version: '4.7'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: activerecord
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -122,6 +122,8 @@ files:
|
|
|
122
122
|
- LICENSE.txt
|
|
123
123
|
- README.md
|
|
124
124
|
- Rakefile
|
|
125
|
+
- gemfiles/mongoid3.gemfile
|
|
126
|
+
- gemfiles/mongoid4.gemfile
|
|
125
127
|
- lib/searchkick.rb
|
|
126
128
|
- lib/searchkick/logger.rb
|
|
127
129
|
- lib/searchkick/model.rb
|
|
@@ -135,6 +137,7 @@ files:
|
|
|
135
137
|
- test/autocomplete_test.rb
|
|
136
138
|
- test/boost_test.rb
|
|
137
139
|
- test/facets_test.rb
|
|
140
|
+
- test/highlight_test.rb
|
|
138
141
|
- test/index_test.rb
|
|
139
142
|
- test/inheritance_test.rb
|
|
140
143
|
- test/match_test.rb
|
|
@@ -171,6 +174,7 @@ test_files:
|
|
|
171
174
|
- test/autocomplete_test.rb
|
|
172
175
|
- test/boost_test.rb
|
|
173
176
|
- test/facets_test.rb
|
|
177
|
+
- test/highlight_test.rb
|
|
174
178
|
- test/index_test.rb
|
|
175
179
|
- test/inheritance_test.rb
|
|
176
180
|
- test/match_test.rb
|