searchkick 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +38 -0
- data/README.md +21 -0
- data/lib/searchkick.rb +1 -0
- data/lib/searchkick/model.rb +1 -0
- data/lib/searchkick/reindex.rb +8 -2
- data/lib/searchkick/search.rb +192 -140
- data/lib/searchkick/similar.rb +19 -0
- data/lib/searchkick/tasks.rb +27 -3
- data/lib/searchkick/version.rb +1 -1
- data/test/autocomplete_test.rb +20 -0
- data/test/boost_test.rb +4 -3
- data/test/index_test.rb +11 -2
- data/test/match_test.rb +9 -19
- data/test/similar_test.rb +10 -0
- data/test/sql_test.rb +12 -3
- data/test/test_helper.rb +4 -2
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 275937983ec78c77a0d7f25fa26a9fbe067a5154
|
4
|
+
data.tar.gz: 31783a30cf0b0d6170d94a99daf2c0022d20008d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 636392d9b685813dbdcfd6d8b9f9aa715be73515e089f32a1c95a870c3c0ff17a3a336c3605318b9f8ea6ca406927493752fa6d603762f70a77777de41306144
|
7
|
+
data.tar.gz: 954203fe3c5a0e48d404322c7a270cde965bbd5f69e32e285da9e1582546cd5257bd87b616315c3e997db9402838f4966920fd5c0e10c195502b0b54214a64ff
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
## 0.2.3
|
2
|
+
|
3
|
+
- Setup Travis
|
4
|
+
- Clean old indices before reindex
|
5
|
+
- Search for `*` returns all results
|
6
|
+
- Fixed pagination
|
7
|
+
- Added `similar` method
|
8
|
+
|
9
|
+
## 0.2.2
|
10
|
+
|
11
|
+
- Clean old indices after reindex
|
12
|
+
- More expansions for fuzzy queries
|
13
|
+
|
14
|
+
## 0.2.1
|
15
|
+
|
16
|
+
- Added Rails logger
|
17
|
+
- Only fetch ids when `load: true`
|
18
|
+
|
19
|
+
## 0.2.0
|
20
|
+
|
21
|
+
- Added autocomplete
|
22
|
+
- Added “Did you mean” suggestions
|
23
|
+
- Added personalized searches
|
24
|
+
|
25
|
+
## 0.1.4
|
26
|
+
|
27
|
+
- Bug fix
|
28
|
+
|
29
|
+
## 0.1.3
|
30
|
+
|
31
|
+
- Changed edit distance to one for misspellings
|
32
|
+
- Raise errors when indexing fails
|
33
|
+
- Fixed pagination
|
34
|
+
- Fixed :include option
|
35
|
+
|
36
|
+
## 0.1.2
|
37
|
+
|
38
|
+
- Launch
|
data/README.md
CHANGED
@@ -22,6 +22,8 @@ Plus:
|
|
22
22
|
|
23
23
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com)
|
24
24
|
|
25
|
+
[![Build Status](https://travis-ci.org/ankane/searchkick.png?branch=master)](https://travis-ci.org/ankane/searchkick)
|
26
|
+
|
25
27
|
## Get Started
|
26
28
|
|
27
29
|
[Install Elasticsearch](http://www.elasticsearch.org/guide/reference/setup/installation/). For Homebrew, use:
|
@@ -282,6 +284,15 @@ Advanced
|
|
282
284
|
Product.search "2% Milk", facets: {store_id: {where: {in_stock: true}}}
|
283
285
|
```
|
284
286
|
|
287
|
+
### Similar Items
|
288
|
+
|
289
|
+
Find similar items.
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
product = Product.first
|
293
|
+
product.similar(fields: ["name"])
|
294
|
+
```
|
295
|
+
|
285
296
|
## Deployment
|
286
297
|
|
287
298
|
Searchkick uses `ENV["ELASTICSEARCH_URL"]` for the Elasticsearch server. This defaults to `http://localhost:9200`.
|
@@ -361,6 +372,12 @@ Do not load models
|
|
361
372
|
Product.search "milk", load: false
|
362
373
|
```
|
363
374
|
|
375
|
+
Reindex all models (Rails only)
|
376
|
+
|
377
|
+
```sh
|
378
|
+
rake searchkick:reindex:all
|
379
|
+
```
|
380
|
+
|
364
381
|
## Migrating from Tire
|
365
382
|
|
366
383
|
1. Change `search` methods to `tire.search` and add index name in existing search calls
|
@@ -411,6 +428,10 @@ Thanks to Karel Minarik for [Tire](https://github.com/karmi/tire), Jaroslav Kali
|
|
411
428
|
|
412
429
|
- Make Searchkick work with any language
|
413
430
|
|
431
|
+
## History
|
432
|
+
|
433
|
+
View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md)
|
434
|
+
|
414
435
|
## Contributing
|
415
436
|
|
416
437
|
1. Fork it
|
data/lib/searchkick.rb
CHANGED
data/lib/searchkick/model.rb
CHANGED
data/lib/searchkick/reindex.rb
CHANGED
@@ -4,9 +4,11 @@ module Searchkick
|
|
4
4
|
# https://gist.github.com/jarosan/3124884
|
5
5
|
def reindex
|
6
6
|
alias_name = tire.index.name
|
7
|
-
new_index = alias_name + "_" + Time.now.strftime("%Y%m%d%H%M%S")
|
7
|
+
new_index = alias_name + "_" + Time.now.strftime("%Y%m%d%H%M%S%L")
|
8
8
|
index = Tire::Index.new(new_index)
|
9
9
|
|
10
|
+
clean_indices
|
11
|
+
|
10
12
|
success = index.create searchkick_index_options
|
11
13
|
raise index.response.to_s if !success
|
12
14
|
|
@@ -36,13 +38,17 @@ module Searchkick
|
|
36
38
|
# remove old indices that start w/ index_name
|
37
39
|
def clean_indices
|
38
40
|
all_indices = JSON.parse(Tire::Configuration.client.get("#{Tire::Configuration.url}/_aliases").body)
|
39
|
-
indices = all_indices.select{|k, v| v["aliases"].empty? && k =~ /\A#{Regexp.escape(index_name)}_\d{14}\z/ }.keys
|
41
|
+
indices = all_indices.select{|k, v| v["aliases"].empty? && k =~ /\A#{Regexp.escape(index_name)}_\d{14,17}\z/ }.keys
|
40
42
|
indices.each do |index|
|
41
43
|
Tire::Index.new(index).delete
|
42
44
|
end
|
43
45
|
indices
|
44
46
|
end
|
45
47
|
|
48
|
+
def self.extended(klass)
|
49
|
+
(@descendents ||= []) << klass
|
50
|
+
end
|
51
|
+
|
46
52
|
private
|
47
53
|
|
48
54
|
def searchkick_index_options
|
data/lib/searchkick/search.rb
CHANGED
@@ -25,166 +25,214 @@ module Searchkick
|
|
25
25
|
load = (options[:include] ? {include: options[:include]} : true) if load
|
26
26
|
|
27
27
|
# pagination
|
28
|
-
page =
|
28
|
+
page = [options[:page].to_i, 1].max
|
29
29
|
per_page = options[:limit] || options[:per_page] || 100000
|
30
|
-
offset = options[:offset] || (page
|
30
|
+
offset = options[:offset] || (page - 1) * per_page
|
31
31
|
index_name = options[:index_name] || index.name
|
32
32
|
|
33
33
|
conversions_field = @searchkick_options[:conversions]
|
34
34
|
personalize_field = @searchkick_options[:personalize]
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
36
|
+
all = term == "*"
|
37
|
+
|
38
|
+
if options[:similar]
|
39
|
+
payload = {
|
40
|
+
more_like_this: {
|
41
|
+
fields: fields,
|
42
|
+
like_text: term,
|
43
|
+
min_doc_freq: 1,
|
44
|
+
min_term_freq: 1
|
45
|
+
}
|
46
|
+
}
|
47
|
+
elsif all
|
48
|
+
payload = {
|
49
|
+
match_all: {}
|
50
|
+
}
|
51
|
+
else
|
52
|
+
if options[:autocomplete]
|
53
|
+
payload = {
|
54
|
+
multi_match: {
|
55
|
+
fields: fields,
|
56
|
+
query: term,
|
57
|
+
analyzer: "searchkick_autocomplete_search"
|
58
|
+
}
|
59
|
+
}
|
60
|
+
else
|
61
|
+
shared_options = {
|
62
|
+
fields: fields,
|
63
|
+
query: term,
|
64
|
+
use_dis_max: false,
|
65
|
+
operator: operator
|
66
|
+
}
|
67
|
+
payload = {
|
68
|
+
dis_max: {
|
69
|
+
queries: [
|
70
|
+
{multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search")},
|
71
|
+
{multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")},
|
72
|
+
{multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search")},
|
73
|
+
{multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search2")}
|
74
|
+
]
|
75
|
+
}
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
if conversions_field and options[:conversions] != false
|
80
|
+
# wrap payload in a bool query
|
81
|
+
payload = {
|
82
|
+
bool: {
|
83
|
+
must: payload,
|
84
|
+
should: {
|
85
|
+
nested: {
|
86
|
+
path: conversions_field,
|
87
|
+
score_mode: "total",
|
88
|
+
query: {
|
89
|
+
custom_score: {
|
90
|
+
query: {
|
91
|
+
match: {
|
92
|
+
query: term
|
93
|
+
}
|
94
|
+
},
|
95
|
+
script: "doc['count'].value"
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
custom_filters = []
|
106
|
+
|
107
|
+
if options[:boost]
|
108
|
+
custom_filters << {
|
109
|
+
filter: {
|
110
|
+
exists: {
|
111
|
+
field: options[:boost]
|
112
|
+
}
|
113
|
+
},
|
114
|
+
script: "log(doc['#{options[:boost]}'].value + 2.718281828)"
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
if options[:user_id] and personalize_field
|
119
|
+
custom_filters << {
|
120
|
+
filter: {
|
121
|
+
term: {
|
122
|
+
personalize_field => options[:user_id]
|
123
|
+
}
|
124
|
+
},
|
125
|
+
boost: 100
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
if custom_filters.any?
|
130
|
+
payload = {
|
131
|
+
custom_filters_score: {
|
132
|
+
query: payload,
|
133
|
+
filters: custom_filters,
|
134
|
+
score_mode: "total"
|
135
|
+
}
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
payload = {
|
140
|
+
query: payload,
|
141
|
+
size: per_page,
|
142
|
+
from: offset
|
143
|
+
}
|
144
|
+
payload[:explain] = options[:explain] if options[:explain]
|
145
|
+
|
146
|
+
# order
|
147
|
+
if options[:order]
|
148
|
+
order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
149
|
+
payload[:sort] = order
|
150
|
+
end
|
151
|
+
|
152
|
+
# where
|
153
|
+
# TODO expand or
|
154
|
+
where_filters =
|
155
|
+
proc do |where|
|
156
|
+
filters = []
|
157
|
+
(where || {}).each do |field, value|
|
158
|
+
if field == :or
|
159
|
+
value.each do |or_clause|
|
160
|
+
filters << {or: or_clause.map{|or_statement| {term: or_statement} }}
|
87
161
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
from offset if offset
|
93
|
-
explain options[:explain] if options[:explain]
|
94
|
-
|
95
|
-
# order
|
96
|
-
if options[:order]
|
97
|
-
order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
98
|
-
sort do
|
99
|
-
order.each do |k, v|
|
100
|
-
by k, v
|
162
|
+
else
|
163
|
+
# expand ranges
|
164
|
+
if value.is_a?(Range)
|
165
|
+
value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last}
|
101
166
|
end
|
102
|
-
end
|
103
|
-
end
|
104
167
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
else
|
116
|
-
# expand ranges
|
117
|
-
if value.is_a?(Range)
|
118
|
-
value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last}
|
119
|
-
end
|
120
|
-
|
121
|
-
if value.is_a?(Array) # in query
|
122
|
-
filters << {terms: {field => value}}
|
123
|
-
elsif value.is_a?(Hash)
|
124
|
-
value.each do |op, op_value|
|
125
|
-
if op == :not # not equal
|
126
|
-
if op_value.is_a?(Array)
|
127
|
-
filters << {not: {terms: {field => op_value}}}
|
128
|
-
else
|
129
|
-
filters << {not: {term: {field => op_value}}}
|
130
|
-
end
|
131
|
-
else
|
132
|
-
range_query =
|
133
|
-
case op
|
134
|
-
when :gt
|
135
|
-
{from: op_value, include_lower: false}
|
136
|
-
when :gte
|
137
|
-
{from: op_value, include_lower: true}
|
138
|
-
when :lt
|
139
|
-
{to: op_value, include_upper: false}
|
140
|
-
when :lte
|
141
|
-
{to: op_value, include_upper: true}
|
142
|
-
else
|
143
|
-
raise "Unknown where operator"
|
144
|
-
end
|
145
|
-
filters << {range: {field => range_query}}
|
146
|
-
end
|
168
|
+
if value.is_a?(Array) # in query
|
169
|
+
filters << {terms: {field => value}}
|
170
|
+
elsif value.is_a?(Hash)
|
171
|
+
value.each do |op, op_value|
|
172
|
+
if op == :not # not equal
|
173
|
+
if op_value.is_a?(Array)
|
174
|
+
filters << {not: {terms: {field => op_value}}}
|
175
|
+
else
|
176
|
+
filters << {not: {term: {field => op_value}}}
|
147
177
|
end
|
148
178
|
else
|
149
|
-
|
179
|
+
range_query =
|
180
|
+
case op
|
181
|
+
when :gt
|
182
|
+
{from: op_value, include_lower: false}
|
183
|
+
when :gte
|
184
|
+
{from: op_value, include_lower: true}
|
185
|
+
when :lt
|
186
|
+
{to: op_value, include_upper: false}
|
187
|
+
when :lte
|
188
|
+
{to: op_value, include_upper: true}
|
189
|
+
else
|
190
|
+
raise "Unknown where operator"
|
191
|
+
end
|
192
|
+
filters << {range: {field => range_query}}
|
150
193
|
end
|
151
194
|
end
|
195
|
+
else
|
196
|
+
filters << {term: {field => value}}
|
152
197
|
end
|
153
|
-
filters
|
154
198
|
end
|
155
|
-
|
156
|
-
where_filters.call(options[:where]).each do |f|
|
157
|
-
type, value = f.first
|
158
|
-
filter type, value
|
159
199
|
end
|
200
|
+
filters
|
201
|
+
end
|
160
202
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
203
|
+
# filters
|
204
|
+
filters = where_filters.call(options[:where])
|
205
|
+
if filters.any?
|
206
|
+
payload[:filter] = {
|
207
|
+
and: filters
|
208
|
+
}
|
209
|
+
end
|
167
210
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
type, value = facet_filters.first.first
|
174
|
-
facet_filter type, value
|
175
|
-
elsif facet_filters.size > 1
|
176
|
-
facet_filter :and, *facet_filters
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
211
|
+
# facets
|
212
|
+
if options[:facets]
|
213
|
+
facets = options[:facets] || {}
|
214
|
+
if facets.is_a?(Array) # convert to more advanced syntax
|
215
|
+
facets = Hash[ facets.map{|f| [f, {}] } ]
|
181
216
|
end
|
182
217
|
|
183
|
-
|
218
|
+
payload[:facets] = {}
|
219
|
+
facets.each do |field, facet_options|
|
220
|
+
payload[:facets][field] = {
|
221
|
+
terms: {
|
222
|
+
field: field
|
223
|
+
}
|
224
|
+
}
|
184
225
|
|
185
|
-
|
186
|
-
|
187
|
-
|
226
|
+
facet_filters = where_filters.call(facet_options[:where])
|
227
|
+
if facet_filters
|
228
|
+
payload[:facets][field][:facet_filter] = {
|
229
|
+
and: {
|
230
|
+
filters: facet_filters
|
231
|
+
}
|
232
|
+
}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
188
236
|
|
189
237
|
# suggestions
|
190
238
|
if options[:suggest]
|
@@ -203,7 +251,11 @@ module Searchkick
|
|
203
251
|
end
|
204
252
|
end
|
205
253
|
|
206
|
-
|
254
|
+
# An empty array will cause only the _id and _type for each hit to be returned
|
255
|
+
# http://www.elasticsearch.org/guide/reference/api/search/fields/
|
256
|
+
payload[:fields] = [] if load
|
257
|
+
|
258
|
+
search = Tire::Search::Search.new(index_name, load: load, payload: payload, size: per_page, from: offset)
|
207
259
|
Searchkick::Results.new(search.json, search.options.merge(term: term))
|
208
260
|
end
|
209
261
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Searchkick
|
2
|
+
module Similar
|
3
|
+
|
4
|
+
def similar(options = {})
|
5
|
+
like_text = index.retrieve(document_type, id).to_hash
|
6
|
+
.keep_if{|k,v| k[0] != "_" and (!options[:fields] or options[:fields].map(&:to_sym).include?(k)) }
|
7
|
+
.values.compact.join(" ")
|
8
|
+
|
9
|
+
# TODO deep merge method
|
10
|
+
options[:where] ||= {}
|
11
|
+
options[:where][:_id] ||= {}
|
12
|
+
options[:where][:_id][:not] = id
|
13
|
+
options[:limit] ||= 10
|
14
|
+
options[:similar] = true
|
15
|
+
self.class.search(like_text, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/searchkick/tasks.rb
CHANGED
@@ -1,9 +1,33 @@
|
|
1
1
|
require "rake"
|
2
2
|
|
3
3
|
namespace :searchkick do
|
4
|
-
|
4
|
+
|
5
|
+
desc "reindex model"
|
5
6
|
task :reindex => :environment do
|
6
|
-
|
7
|
-
|
7
|
+
if ENV["CLASS"]
|
8
|
+
klass = ENV["CLASS"].constantize rescue nil
|
9
|
+
if klass
|
10
|
+
klass.reindex
|
11
|
+
else
|
12
|
+
abort "Could not find class: #{ENV["CLASS"]}"
|
13
|
+
end
|
14
|
+
else
|
15
|
+
abort "USAGE: rake searchkick:reindex CLASS=Product"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if defined?(Rails)
|
20
|
+
|
21
|
+
namespace :reindex do
|
22
|
+
desc "reindex all models"
|
23
|
+
task :all => :environment do
|
24
|
+
Rails.application.eager_load!
|
25
|
+
(Searchkick::Reindex.instance_variable_get(:@descendents) || []).each do |model|
|
26
|
+
model.reindex
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
8
31
|
end
|
32
|
+
|
9
33
|
end
|
data/lib/searchkick/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class TestAutocomplete < Minitest::Unit::TestCase
|
4
|
+
|
5
|
+
def test_autocomplete
|
6
|
+
store_names ["Hummus"]
|
7
|
+
assert_search "hum", ["Hummus"], autocomplete: true
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_autocomplete_two_words
|
11
|
+
store_names ["Organic Hummus"]
|
12
|
+
assert_search "hum", [], autocomplete: true
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_autocomplete_fields
|
16
|
+
store_names ["Hummus"]
|
17
|
+
assert_search "hum", ["Hummus"], autocomplete: true, fields: [:name]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/test/boost_test.rb
CHANGED
@@ -25,10 +25,11 @@ class TestBoost < Minitest::Unit::TestCase
|
|
25
25
|
|
26
26
|
def test_boost
|
27
27
|
store [
|
28
|
-
{name: "
|
29
|
-
{name: "Tomato B", orders_count: 10}
|
28
|
+
{name: "Tomato A"},
|
29
|
+
{name: "Tomato B", orders_count: 10},
|
30
|
+
{name: "Tomato C", orders_count: 100}
|
30
31
|
]
|
31
|
-
assert_order "tomato", ["Tomato B", "
|
32
|
+
assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost: "orders_count"
|
32
33
|
end
|
33
34
|
|
34
35
|
def test_boost_zero
|
data/test/index_test.rb
CHANGED
@@ -3,8 +3,8 @@ require_relative "test_helper"
|
|
3
3
|
class TestIndex < Minitest::Unit::TestCase
|
4
4
|
|
5
5
|
def test_clean_indices
|
6
|
-
old_index = Tire::Index.new("
|
7
|
-
different_index = Tire::Index.new("
|
6
|
+
old_index = Tire::Index.new("products_test_20130801000000000")
|
7
|
+
different_index = Tire::Index.new("items_test_20130801000000000")
|
8
8
|
|
9
9
|
# create indexes
|
10
10
|
old_index.create
|
@@ -17,4 +17,13 @@ class TestIndex < Minitest::Unit::TestCase
|
|
17
17
|
assert !old_index.exists?
|
18
18
|
end
|
19
19
|
|
20
|
+
def test_clean_indices_old_format
|
21
|
+
old_index = Tire::Index.new("products_test_20130801000000")
|
22
|
+
old_index.create
|
23
|
+
|
24
|
+
Product.clean_indices
|
25
|
+
|
26
|
+
assert !old_index.exists?
|
27
|
+
end
|
28
|
+
|
20
29
|
end
|
data/test/match_test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require_relative "test_helper"
|
2
4
|
|
3
5
|
class TestMatch < Minitest::Unit::TestCase
|
@@ -19,10 +21,10 @@ class TestMatch < Minitest::Unit::TestCase
|
|
19
21
|
assert_search "pepperjack cheese skewers", ["Pepper Jack Cheese Skewers"]
|
20
22
|
end
|
21
23
|
|
22
|
-
def test_cheese_space_in_query
|
23
|
-
|
24
|
-
|
25
|
-
end
|
24
|
+
# def test_cheese_space_in_query
|
25
|
+
# store_names ["Pepperjack Cheese Skewers"]
|
26
|
+
# assert_search "pepper jack cheese skewers", ["Pepperjack Cheese Skewers"]
|
27
|
+
# end
|
26
28
|
|
27
29
|
def test_middle_token
|
28
30
|
store_names ["Dish Washer Amazing Organic Soap"]
|
@@ -127,21 +129,9 @@ class TestMatch < Minitest::Unit::TestCase
|
|
127
129
|
assert_search "almondmilks", ["Almond Milk"]
|
128
130
|
end
|
129
131
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
store_names ["Hummus"]
|
134
|
-
assert_search "hum", ["Hummus"], autocomplete: true
|
135
|
-
end
|
136
|
-
|
137
|
-
def test_autocomplete_two_words
|
138
|
-
store_names ["Organic Hummus"]
|
139
|
-
assert_search "hum", [], autocomplete: true
|
140
|
-
end
|
141
|
-
|
142
|
-
def test_autocomplete_fields
|
143
|
-
store_names ["Hummus"]
|
144
|
-
assert_search "hum", ["Hummus"], autocomplete: true, fields: [:name]
|
132
|
+
def test_all
|
133
|
+
store_names ["Product A", "Product B"]
|
134
|
+
assert_search "*", ["Product A", "Product B"]
|
145
135
|
end
|
146
136
|
|
147
137
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class TestSimilar < Minitest::Unit::TestCase
|
4
|
+
|
5
|
+
def test_fields
|
6
|
+
store_names ["1% Organic Milk", "2% Organic Milk", "Popcorn"]
|
7
|
+
assert_equal ["2% Organic Milk"], Product.find_by(name: "1% Organic Milk").similar(fields: ["name"]).map(&:name)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
data/test/sql_test.rb
CHANGED
@@ -20,18 +20,25 @@ class TestSql < Minitest::Unit::TestCase
|
|
20
20
|
|
21
21
|
def test_pagination
|
22
22
|
store_names ["Product A", "Product B", "Product C", "Product D", "Product E"]
|
23
|
-
|
23
|
+
products = Product.search("product", order: {name: :asc}, page: 2, per_page: 2)
|
24
|
+
assert_equal ["Product C", "Product D"], products.map(&:name)
|
25
|
+
assert_equal 2, products.current_page
|
26
|
+
assert_equal 2, products.per_page
|
27
|
+
assert_equal 3, products.total_pages
|
28
|
+
assert_equal 5, products.total_count
|
24
29
|
end
|
25
30
|
|
26
31
|
def test_pagination_nil_page
|
27
32
|
store_names ["Product A", "Product B", "Product C", "Product D", "Product E"]
|
28
|
-
|
33
|
+
products = Product.search("product", order: {name: :asc}, page: nil, per_page: 2)
|
34
|
+
assert_equal ["Product A", "Product B"], products.map(&:name)
|
35
|
+
assert_equal 1, products.current_page
|
29
36
|
end
|
30
37
|
|
31
38
|
def test_where
|
32
39
|
now = Time.now
|
33
40
|
store [
|
34
|
-
{name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4},
|
41
|
+
{name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4, user_ids: [1, 2, 3]},
|
35
42
|
{name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3},
|
36
43
|
{name: "Product C", store_id: 3, in_stock: false, backordered: true, created_at: now - 2, orders_count: 2},
|
37
44
|
{name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1},
|
@@ -54,6 +61,8 @@ class TestSql < Minitest::Unit::TestCase
|
|
54
61
|
assert_search "product", ["Product B", "Product C", "Product D"], where: {store_id: {not: 1}}
|
55
62
|
assert_search "product", ["Product C", "Product D"], where: {store_id: {not: [1, 2]}}
|
56
63
|
assert_search "product", ["Product A", "Product B", "Product C"], where: {or: [[{in_stock: true}, {store_id: 3}]]}
|
64
|
+
# array
|
65
|
+
assert_search "product", ["Product A"], where: {user_ids: 2}
|
57
66
|
end
|
58
67
|
|
59
68
|
def test_where_string
|
data/test/test_helper.rb
CHANGED
@@ -4,6 +4,8 @@ require "minitest/autorun"
|
|
4
4
|
require "minitest/pride"
|
5
5
|
require "active_record"
|
6
6
|
|
7
|
+
ENV["RACK_ENV"] = "test"
|
8
|
+
|
7
9
|
# for debugging
|
8
10
|
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
9
11
|
|
@@ -38,7 +40,8 @@ class Product < ActiveRecord::Base
|
|
38
40
|
|
39
41
|
searchkick \
|
40
42
|
settings: {
|
41
|
-
number_of_shards: 1
|
43
|
+
number_of_shards: 1,
|
44
|
+
number_of_replicas: 0
|
42
45
|
},
|
43
46
|
synonyms: [
|
44
47
|
["clorox", "bleach"],
|
@@ -65,7 +68,6 @@ end
|
|
65
68
|
|
66
69
|
Product.index.delete if Product.index.exists?
|
67
70
|
Product.reindex
|
68
|
-
sleep 1
|
69
71
|
Product.reindex # run twice for both index paths
|
70
72
|
|
71
73
|
class MiniTest::Unit::TestCase
|
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.2.
|
4
|
+
version: 0.2.3
|
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-08-
|
11
|
+
date: 2013-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tire
|
@@ -116,6 +116,8 @@ extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
118
|
- .gitignore
|
119
|
+
- .travis.yml
|
120
|
+
- CHANGELOG.md
|
119
121
|
- Gemfile
|
120
122
|
- LICENSE.txt
|
121
123
|
- README.md
|
@@ -126,13 +128,16 @@ files:
|
|
126
128
|
- lib/searchkick/reindex.rb
|
127
129
|
- lib/searchkick/results.rb
|
128
130
|
- lib/searchkick/search.rb
|
131
|
+
- lib/searchkick/similar.rb
|
129
132
|
- lib/searchkick/tasks.rb
|
130
133
|
- lib/searchkick/version.rb
|
131
134
|
- searchkick.gemspec
|
135
|
+
- test/autocomplete_test.rb
|
132
136
|
- test/boost_test.rb
|
133
137
|
- test/facets_test.rb
|
134
138
|
- test/index_test.rb
|
135
139
|
- test/match_test.rb
|
140
|
+
- test/similar_test.rb
|
136
141
|
- test/sql_test.rb
|
137
142
|
- test/suggest_test.rb
|
138
143
|
- test/synonyms_test.rb
|
@@ -162,10 +167,12 @@ signing_key:
|
|
162
167
|
specification_version: 4
|
163
168
|
summary: Search made easy
|
164
169
|
test_files:
|
170
|
+
- test/autocomplete_test.rb
|
165
171
|
- test/boost_test.rb
|
166
172
|
- test/facets_test.rb
|
167
173
|
- test/index_test.rb
|
168
174
|
- test/match_test.rb
|
175
|
+
- test/similar_test.rb
|
169
176
|
- test/sql_test.rb
|
170
177
|
- test/suggest_test.rb
|
171
178
|
- test/synonyms_test.rb
|