searchkick 0.7.8 → 0.7.9
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/CHANGELOG.md +8 -0
- data/Gemfile +1 -1
- data/README.md +48 -7
- data/lib/searchkick/index.rb +4 -0
- data/lib/searchkick/query.rb +222 -211
- data/lib/searchkick/reindex.rb +8 -3
- data/lib/searchkick/results.rb +4 -1
- data/lib/searchkick/version.rb +1 -1
- data/test/autocomplete_test.rb +7 -0
- data/test/highlight_test.rb +19 -0
- data/test/index_test.rb +14 -0
- data/test/sql_test.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2bc8db79f3c60a5694ccd3400713d0dc8788ffd
|
4
|
+
data.tar.gz: 163dfde8a0f72d3f9584ad7e6f94b6370bafceb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83f9f13181e205843a72d3539b4ebba790c98f3abdbb9f1be1a3bfda539eb415304bd8e9eaf9acea062f64f6dbbc227db0892cd76623178d0ab20783119a91d4
|
7
|
+
data.tar.gz: 2123d48c3269eb517108f8864740bb2d1cfdc3e0130ed99c161a51d09d97f24799625fc83896912ce4351cfd678434665cefcf176935c2b08f5b5006a23d031a
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -42,7 +42,13 @@ brew install elasticsearch
|
|
42
42
|
Add this line to your application’s Gemfile:
|
43
43
|
|
44
44
|
```ruby
|
45
|
-
gem
|
45
|
+
gem 'searchkick'
|
46
|
+
```
|
47
|
+
|
48
|
+
For Elasticsearch 1.2, use:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
gem 'searchkick', github: 'ankane/searchkick', branch: 'elasticsearch-1.2'
|
46
52
|
```
|
47
53
|
|
48
54
|
For Elasticsearch 0.90, use version `0.6.3` and [this readme](https://github.com/ankane/searchkick/blob/v0.6.3/README.md).
|
@@ -70,6 +76,8 @@ products.each do |product|
|
|
70
76
|
end
|
71
77
|
```
|
72
78
|
|
79
|
+
Searchkick supports the complete [Elasticsearch Search API](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-search.html). As your search becomes more advanced, we recommend you use the [Elasticsearch DSL](#advanced) for maximum flexibility.
|
80
|
+
|
73
81
|
### Queries
|
74
82
|
|
75
83
|
Query like SQL
|
@@ -78,8 +86,6 @@ Query like SQL
|
|
78
86
|
Product.search "2% Milk", where: {in_stock: true}, limit: 10, offset: 50
|
79
87
|
```
|
80
88
|
|
81
|
-
**Note:** If you prefer the Elasticsearch DSL, see the [Advanced section](#advanced)
|
82
|
-
|
83
89
|
Search specific fields
|
84
90
|
|
85
91
|
```ruby
|
@@ -128,16 +134,18 @@ Boost by the value of a field
|
|
128
134
|
|
129
135
|
```ruby
|
130
136
|
boost_by: [:orders_count] # give popular documents a little boost
|
131
|
-
boost_by: {orders_count: {factor: 10}}
|
137
|
+
boost_by: {orders_count: {factor: 10}} # default factor is 1
|
132
138
|
```
|
133
139
|
|
134
140
|
Boost matching documents
|
135
141
|
|
136
142
|
```ruby
|
137
|
-
boost_where: {user_id: 1}
|
143
|
+
boost_where: {user_id: 1} # default factor is 1000
|
138
144
|
boost_where: {user_id: {value: 1, factor: 100}}
|
139
145
|
```
|
140
146
|
|
147
|
+
[Conversions](#keep-getting-better) are also a great way to boost.
|
148
|
+
|
141
149
|
### Get Everything
|
142
150
|
|
143
151
|
Use a `*` for the query.
|
@@ -204,6 +212,12 @@ To boost fields, use:
|
|
204
212
|
fields: [{"name^2" => :word_start}] # better interface on the way
|
205
213
|
```
|
206
214
|
|
215
|
+
### Exact Matches
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
User.search "hi@searchkick.org", fields: [{email: :exact}]
|
219
|
+
```
|
220
|
+
|
207
221
|
### Language
|
208
222
|
|
209
223
|
Searchkick defaults to English for stemming. To change this, use:
|
@@ -337,7 +351,7 @@ class Product < ActiveRecord::Base
|
|
337
351
|
def search_data
|
338
352
|
{
|
339
353
|
name: name,
|
340
|
-
|
354
|
+
orderer_ids: orders.pluck(:user_id) # boost this product for these users
|
341
355
|
}
|
342
356
|
end
|
343
357
|
|
@@ -347,7 +361,7 @@ end
|
|
347
361
|
Reindex and search with:
|
348
362
|
|
349
363
|
```ruby
|
350
|
-
Product.search "milk", boost_where: {
|
364
|
+
Product.search "milk", boost_where: {orderer_ids: current_user.id}
|
351
365
|
```
|
352
366
|
|
353
367
|
### Autocomplete
|
@@ -557,6 +571,33 @@ Animal.search "*", type: [Dog, Cat] # just cats and dogs
|
|
557
571
|
Dog.search "airbudd", suggest: true # suggestions for all animals
|
558
572
|
```
|
559
573
|
|
574
|
+
## Debugging Queries
|
575
|
+
|
576
|
+
See how Elasticsearch tokenizes your queries with:
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
Product.searchkick_index.tokens("Dish Washer Soap", analyzer: "default_index")
|
580
|
+
# ["dish", "dishwash", "washer", "washersoap", "soap"]
|
581
|
+
|
582
|
+
Product.searchkick_index.tokens("dishwasher soap", analyzer: "searchkick_search")
|
583
|
+
# ["dishwashersoap"] - no match
|
584
|
+
|
585
|
+
Product.searchkick_index.tokens("dishwasher soap", analyzer: "searchkick_search2")
|
586
|
+
# ["dishwash", "soap"] - match!!
|
587
|
+
```
|
588
|
+
|
589
|
+
Partial matches
|
590
|
+
|
591
|
+
```ruby
|
592
|
+
Product.searchkick_index.tokens("San Diego", analyzer: "searchkick_word_start_index")
|
593
|
+
# ["s", "sa", "san", "d", "di", "die", "dieg", "diego"]
|
594
|
+
|
595
|
+
Product.searchkick_index.tokens("dieg", analyzer: "searchkick_word_search")
|
596
|
+
# ["dieg"] - match!!
|
597
|
+
```
|
598
|
+
|
599
|
+
See the [complete list of analyzers](lib/searchkick/reindex.rb#L86).
|
600
|
+
|
560
601
|
## Deployment
|
561
602
|
|
562
603
|
Searchkick uses `ENV["ELASTICSEARCH_URL"]` for the Elasticsearch server. This defaults to `http://localhost:9200`.
|
data/lib/searchkick/index.rb
CHANGED
data/lib/searchkick/query.rb
CHANGED
@@ -45,276 +45,286 @@ module Searchkick
|
|
45
45
|
padding = [options[:padding].to_i, 0].max
|
46
46
|
offset = options[:offset] || (page - 1) * per_page + padding
|
47
47
|
|
48
|
+
# model and eagar loading
|
49
|
+
load = options[:load].nil? ? true : options[:load]
|
50
|
+
|
48
51
|
conversions_field = searchkick_options[:conversions]
|
49
52
|
personalize_field = searchkick_options[:personalize]
|
50
53
|
|
51
54
|
all = term == "*"
|
55
|
+
facet_limits = {}
|
52
56
|
|
53
|
-
if options[:
|
54
|
-
payload = options[:
|
55
|
-
elsif options[:similar]
|
56
|
-
payload = {
|
57
|
-
more_like_this: {
|
58
|
-
fields: fields,
|
59
|
-
like_text: term,
|
60
|
-
min_doc_freq: 1,
|
61
|
-
min_term_freq: 1,
|
62
|
-
analyzer: "searchkick_search2"
|
63
|
-
}
|
64
|
-
}
|
65
|
-
elsif all
|
66
|
-
payload = {
|
67
|
-
match_all: {}
|
68
|
-
}
|
57
|
+
if options[:json]
|
58
|
+
payload = options[:json]
|
69
59
|
else
|
70
|
-
if options[:
|
60
|
+
if options[:query]
|
61
|
+
payload = options[:query]
|
62
|
+
elsif options[:similar]
|
71
63
|
payload = {
|
72
|
-
|
64
|
+
more_like_this: {
|
73
65
|
fields: fields,
|
74
|
-
|
75
|
-
|
66
|
+
like_text: term,
|
67
|
+
min_doc_freq: 1,
|
68
|
+
min_term_freq: 1,
|
69
|
+
analyzer: "searchkick_search2"
|
76
70
|
}
|
77
71
|
}
|
72
|
+
elsif all
|
73
|
+
payload = {
|
74
|
+
match_all: {}
|
75
|
+
}
|
78
76
|
else
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
operator: operator,
|
87
|
-
boost: factor
|
77
|
+
if options[:autocomplete]
|
78
|
+
payload = {
|
79
|
+
multi_match: {
|
80
|
+
fields: fields,
|
81
|
+
query: term,
|
82
|
+
analyzer: "searchkick_autocomplete_search"
|
83
|
+
}
|
88
84
|
}
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
]
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
85
|
+
else
|
86
|
+
queries = []
|
87
|
+
fields.each do |field|
|
88
|
+
qs = []
|
89
|
+
|
90
|
+
factor = boost_fields[field] || 1
|
91
|
+
shared_options = {
|
92
|
+
query: term,
|
93
|
+
operator: operator,
|
94
|
+
boost: factor
|
95
|
+
}
|
96
|
+
|
97
|
+
if field == "_all" or field.end_with?(".analyzed")
|
98
|
+
shared_options[:cutoff_frequency] = 0.001 unless operator == "and"
|
99
|
+
qs.concat [
|
100
|
+
shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search"),
|
101
|
+
shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search2")
|
100
102
|
]
|
103
|
+
if options[:misspellings] != false
|
104
|
+
distance = (options[:misspellings].is_a?(Hash) && options[:misspellings][:distance]) || 1
|
105
|
+
qs.concat [
|
106
|
+
shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search"),
|
107
|
+
shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search2")
|
108
|
+
]
|
109
|
+
end
|
110
|
+
elsif field.end_with?(".exact")
|
111
|
+
f = field.split(".")[0..-2].join(".")
|
112
|
+
queries << {match: {f => shared_options.merge(analyzer: "keyword")}}
|
113
|
+
else
|
114
|
+
analyzer = field.match(/\.word_(start|middle|end)\z/) ? "searchkick_word_search" : "searchkick_autocomplete_search"
|
115
|
+
qs << shared_options.merge(analyzer: analyzer)
|
101
116
|
end
|
102
|
-
|
103
|
-
|
104
|
-
queries << {multi_match: shared_options.merge(analyzer: analyzer)}
|
117
|
+
|
118
|
+
queries.concat(qs.map{|q| {match: {field => q}} })
|
105
119
|
end
|
106
|
-
end
|
107
120
|
|
108
|
-
|
109
|
-
|
110
|
-
|
121
|
+
payload = {
|
122
|
+
dis_max: {
|
123
|
+
queries: queries
|
124
|
+
}
|
111
125
|
}
|
112
|
-
|
113
|
-
end
|
126
|
+
end
|
114
127
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
128
|
+
if conversions_field and options[:conversions] != false
|
129
|
+
# wrap payload in a bool query
|
130
|
+
payload = {
|
131
|
+
bool: {
|
132
|
+
must: payload,
|
133
|
+
should: {
|
134
|
+
nested: {
|
135
|
+
path: conversions_field,
|
136
|
+
score_mode: "total",
|
137
|
+
query: {
|
138
|
+
function_score: {
|
139
|
+
boost_mode: "replace",
|
140
|
+
query: {
|
141
|
+
match: {
|
142
|
+
query: term
|
143
|
+
}
|
144
|
+
},
|
145
|
+
script_score: {
|
146
|
+
script: "doc['count'].value"
|
130
147
|
}
|
131
|
-
},
|
132
|
-
script_score: {
|
133
|
-
script: "doc['count'].value"
|
134
148
|
}
|
135
149
|
}
|
136
150
|
}
|
137
151
|
}
|
138
152
|
}
|
139
153
|
}
|
140
|
-
|
154
|
+
end
|
141
155
|
end
|
142
|
-
end
|
143
156
|
|
144
|
-
|
157
|
+
custom_filters = []
|
145
158
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
159
|
+
boost_by = options[:boost_by] || {}
|
160
|
+
if boost_by.is_a?(Array)
|
161
|
+
boost_by = Hash[ boost_by.map{|f| [f, {factor: 1}] } ]
|
162
|
+
end
|
163
|
+
if options[:boost]
|
164
|
+
boost_by[options[:boost]] = {factor: 1}
|
165
|
+
end
|
153
166
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
167
|
+
boost_by.each do |field, value|
|
168
|
+
custom_filters << {
|
169
|
+
filter: {
|
170
|
+
exists: {
|
171
|
+
field: field
|
172
|
+
}
|
173
|
+
},
|
174
|
+
script_score: {
|
175
|
+
script: "#{value[:factor].to_f} * log(doc['#{field}'].value + 2.718281828)"
|
159
176
|
}
|
160
|
-
},
|
161
|
-
script_score: {
|
162
|
-
script: "#{value[:factor].to_f} * log(doc['#{field}'].value + 2.718281828)"
|
163
177
|
}
|
164
|
-
}
|
165
|
-
end
|
166
|
-
|
167
|
-
boost_where = options[:boost_where] || {}
|
168
|
-
if options[:user_id] and personalize_field
|
169
|
-
boost_where[personalize_field] = options[:user_id]
|
170
|
-
end
|
171
|
-
if options[:personalize]
|
172
|
-
boost_where.merge!(options[:personalize])
|
173
|
-
end
|
174
|
-
boost_where.each do |field, value|
|
175
|
-
if value.is_a?(Hash)
|
176
|
-
value, factor = value[:value], value[:factor]
|
177
|
-
else
|
178
|
-
factor = 1000
|
179
178
|
end
|
180
|
-
custom_filters << {
|
181
|
-
filter: {
|
182
|
-
term: {field => value}
|
183
|
-
},
|
184
|
-
boost_factor: factor
|
185
|
-
}
|
186
|
-
end
|
187
179
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
180
|
+
boost_where = options[:boost_where] || {}
|
181
|
+
if options[:user_id] and personalize_field
|
182
|
+
boost_where[personalize_field] = options[:user_id]
|
183
|
+
end
|
184
|
+
if options[:personalize]
|
185
|
+
boost_where.merge!(options[:personalize])
|
186
|
+
end
|
187
|
+
boost_where.each do |field, value|
|
188
|
+
if value.is_a?(Hash)
|
189
|
+
value, factor = value[:value], value[:factor]
|
190
|
+
else
|
191
|
+
factor = 1000
|
192
|
+
end
|
193
|
+
custom_filters << {
|
194
|
+
filter: {
|
195
|
+
term: {field => value}
|
196
|
+
},
|
197
|
+
boost_factor: factor
|
194
198
|
}
|
195
|
-
|
196
|
-
end
|
197
|
-
|
198
|
-
payload = {
|
199
|
-
query: payload,
|
200
|
-
size: per_page,
|
201
|
-
from: offset
|
202
|
-
}
|
203
|
-
payload[:explain] = options[:explain] if options[:explain]
|
199
|
+
end
|
204
200
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
201
|
+
if custom_filters.any?
|
202
|
+
payload = {
|
203
|
+
function_score: {
|
204
|
+
functions: custom_filters,
|
205
|
+
query: payload,
|
206
|
+
score_mode: "sum"
|
207
|
+
}
|
208
|
+
}
|
209
|
+
end
|
210
210
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
and: filters
|
211
|
+
payload = {
|
212
|
+
query: payload,
|
213
|
+
size: per_page,
|
214
|
+
from: offset
|
216
215
|
}
|
217
|
-
|
216
|
+
payload[:explain] = options[:explain] if options[:explain]
|
218
217
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
if facets.is_a?(Array) # convert to more advanced syntax
|
224
|
-
facets = Hash[ facets.map{|f| [f, {}] } ]
|
218
|
+
# order
|
219
|
+
if options[:order]
|
220
|
+
order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
221
|
+
payload[:sort] = Hash[ order.map{|k, v| [k.to_s == "id" ? :_id : k, v] } ]
|
225
222
|
end
|
226
223
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
224
|
+
# filters
|
225
|
+
filters = where_filters(options[:where])
|
226
|
+
if filters.any?
|
227
|
+
payload[:filter] = {
|
228
|
+
and: filters
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
# facets
|
233
|
+
if options[:facets]
|
234
|
+
facets = options[:facets] || {}
|
235
|
+
if facets.is_a?(Array) # convert to more advanced syntax
|
236
|
+
facets = Hash[ facets.map{|f| [f, {}] } ]
|
237
|
+
end
|
238
|
+
|
239
|
+
payload[:facets] = {}
|
240
|
+
facets.each do |field, facet_options|
|
241
|
+
# ask for extra facets due to
|
242
|
+
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
243
|
+
size = facet_options[:limit] ? facet_options[:limit] + 150 : 100000
|
232
244
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
245
|
+
if facet_options[:ranges]
|
246
|
+
payload[:facets][field] = {
|
247
|
+
range: {
|
248
|
+
field.to_sym => facet_options[:ranges]
|
249
|
+
}
|
237
250
|
}
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
251
|
+
elsif facet_options[:stats]
|
252
|
+
payload[:facets][field] = {
|
253
|
+
terms_stats: {
|
254
|
+
key_field: field,
|
255
|
+
value_script: "doc.score",
|
256
|
+
size: size
|
257
|
+
}
|
245
258
|
}
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
259
|
+
else
|
260
|
+
payload[:facets][field] = {
|
261
|
+
terms: {
|
262
|
+
field: field,
|
263
|
+
size: size
|
264
|
+
}
|
252
265
|
}
|
253
|
-
|
254
|
-
end
|
266
|
+
end
|
255
267
|
|
256
|
-
|
268
|
+
facet_limits[field] = facet_options[:limit] if facet_options[:limit]
|
257
269
|
|
258
|
-
|
259
|
-
|
270
|
+
# offset is not possible
|
271
|
+
# http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
|
260
272
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
273
|
+
facet_options.deep_merge!(where: options[:where].reject{|k| k == field}) if options[:smart_facets] == true
|
274
|
+
facet_filters = where_filters(facet_options[:where])
|
275
|
+
if facet_filters.any?
|
276
|
+
payload[:facets][field][:facet_filter] = {
|
277
|
+
and: {
|
278
|
+
filters: facet_filters
|
279
|
+
}
|
267
280
|
}
|
268
|
-
|
281
|
+
end
|
269
282
|
end
|
270
283
|
end
|
271
|
-
end
|
272
284
|
|
273
|
-
|
274
|
-
|
275
|
-
|
285
|
+
# suggestions
|
286
|
+
if options[:suggest]
|
287
|
+
suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
|
276
288
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
289
|
+
# intersection
|
290
|
+
if options[:fields]
|
291
|
+
suggest_fields = suggest_fields & options[:fields].map{|v| (v.is_a?(Hash) ? v.keys.first : v).to_s }
|
292
|
+
end
|
281
293
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
294
|
+
if suggest_fields.any?
|
295
|
+
payload[:suggest] = {text: term}
|
296
|
+
suggest_fields.each do |field|
|
297
|
+
payload[:suggest][field] = {
|
298
|
+
phrase: {
|
299
|
+
field: "#{field}.suggest"
|
300
|
+
}
|
288
301
|
}
|
289
|
-
|
302
|
+
end
|
290
303
|
end
|
291
304
|
end
|
292
|
-
end
|
293
305
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
302
315
|
end
|
303
|
-
end
|
304
316
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
elsif options[:select]
|
313
|
-
payload[:fields] = options[:select]
|
314
|
-
end
|
317
|
+
# An empty array will cause only the _id and _type for each hit to be returned
|
318
|
+
# http://www.elasticsearch.org/guide/reference/api/search/fields/
|
319
|
+
if load
|
320
|
+
payload[:fields] = []
|
321
|
+
elsif options[:select]
|
322
|
+
payload[:fields] = options[:select]
|
323
|
+
end
|
315
324
|
|
316
|
-
|
317
|
-
|
325
|
+
if options[:type] or klass != searchkick_klass
|
326
|
+
@type = [options[:type] || klass].flatten.map{|v| searchkick_index.klass_document_type(v) }
|
327
|
+
end
|
318
328
|
end
|
319
329
|
|
320
330
|
@body = payload
|
@@ -386,7 +396,8 @@ module Searchkick
|
|
386
396
|
per_page: @per_page,
|
387
397
|
padding: @padding,
|
388
398
|
load: @load,
|
389
|
-
includes: options[:include] || options[:includes]
|
399
|
+
includes: options[:include] || options[:includes],
|
400
|
+
json: !options[:json].nil?
|
390
401
|
}
|
391
402
|
Searchkick::Results.new(searchkick_klass, response, opts)
|
392
403
|
end
|
data/lib/searchkick/reindex.rb
CHANGED
@@ -3,7 +3,9 @@ module Searchkick
|
|
3
3
|
|
4
4
|
# https://gist.github.com/jarosan/3124884
|
5
5
|
# http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/
|
6
|
-
def reindex
|
6
|
+
def reindex(options = {})
|
7
|
+
skip_import = options[:import] == false
|
8
|
+
|
7
9
|
alias_name = searchkick_index.name
|
8
10
|
new_name = alias_name + "_" + Time.now.strftime("%Y%m%d%H%M%S%L")
|
9
11
|
index = Searchkick::Index.new(new_name)
|
@@ -14,7 +16,8 @@ module Searchkick
|
|
14
16
|
|
15
17
|
# check if alias exists
|
16
18
|
if Searchkick.client.indices.exists_alias(name: alias_name)
|
17
|
-
|
19
|
+
# import before swap
|
20
|
+
searchkick_import(index) unless skip_import
|
18
21
|
|
19
22
|
# get existing indices to remove
|
20
23
|
old_indices = Searchkick.client.indices.get_alias(name: alias_name).keys
|
@@ -24,7 +27,9 @@ module Searchkick
|
|
24
27
|
else
|
25
28
|
searchkick_index.delete if searchkick_index.exists?
|
26
29
|
Searchkick.client.indices.update_aliases body: {actions: [{add: {index: new_name, alias: alias_name}}]}
|
27
|
-
|
30
|
+
|
31
|
+
# import after swap
|
32
|
+
searchkick_import(index) unless skip_import
|
28
33
|
end
|
29
34
|
|
30
35
|
index.refresh
|
data/lib/searchkick/results.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
module Searchkick
|
2
4
|
class Results
|
3
5
|
include Enumerable
|
@@ -67,7 +69,7 @@ module Searchkick
|
|
67
69
|
each_with_hit.map do |model, hit|
|
68
70
|
details = {}
|
69
71
|
if hit["highlight"]
|
70
|
-
details[:highlight] = Hash[ hit["highlight"].map{|k, v| [k.sub(/\.analyzed\z/, "").to_sym, v.first] } ]
|
72
|
+
details[:highlight] = Hash[ hit["highlight"].map{|k, v| [(options[:json] ? k : k.sub(/\.analyzed\z/, "")).to_sym, v.first] } ]
|
71
73
|
end
|
72
74
|
[model, details]
|
73
75
|
end
|
@@ -112,6 +114,7 @@ module Searchkick
|
|
112
114
|
def previous_page
|
113
115
|
current_page > 1 ? (current_page - 1) : nil
|
114
116
|
end
|
117
|
+
alias_method :prev_page, :previous_page
|
115
118
|
|
116
119
|
def next_page
|
117
120
|
current_page < total_pages ? (current_page + 1) : nil
|
data/lib/searchkick/version.rb
CHANGED
data/test/autocomplete_test.rb
CHANGED
@@ -57,4 +57,11 @@ class TestAutocomplete < Minitest::Unit::TestCase
|
|
57
57
|
assert_search "dark grey", ["Dark Grey"], fields: [{name: :word_start}]
|
58
58
|
end
|
59
59
|
|
60
|
+
# TODO find a better place
|
61
|
+
|
62
|
+
def test_exact
|
63
|
+
store_names ["hi@example.org"]
|
64
|
+
assert_search "hi@example.org", ["hi@example.org"], fields: [{name: :exact}]
|
65
|
+
end
|
66
|
+
|
60
67
|
end
|
data/test/highlight_test.rb
CHANGED
@@ -19,4 +19,23 @@ class TestHighlight < Minitest::Unit::TestCase
|
|
19
19
|
assert_equal "<em>Cinema</em> Orange", highlight[:color]
|
20
20
|
end
|
21
21
|
|
22
|
+
def test_json
|
23
|
+
store_names ["Two Door Cinema Club"]
|
24
|
+
json = {
|
25
|
+
query: {
|
26
|
+
match: {
|
27
|
+
_all: "cinema"
|
28
|
+
}
|
29
|
+
},
|
30
|
+
highlight: {
|
31
|
+
pre_tags: ["<strong>"],
|
32
|
+
post_tags: ["</strong>"],
|
33
|
+
fields: {
|
34
|
+
"name.analyzed" => {}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
assert_equal "Two Door <strong>Cinema</strong> Club", Product.search(json: json).with_details.first[1][:highlight][:"name.analyzed"]
|
39
|
+
end
|
40
|
+
|
22
41
|
end
|
data/test/index_test.rb
CHANGED
@@ -35,6 +35,20 @@ class TestIndex < Minitest::Unit::TestCase
|
|
35
35
|
assert_equal ["Dollar Tree"], Store.search(query: {match: {name: "Dollar Tree"}}).map(&:name)
|
36
36
|
end
|
37
37
|
|
38
|
+
def test_json
|
39
|
+
store_names ["Dollar Tree"], Store
|
40
|
+
assert_equal [], Store.search(query: {match: {name: "dollar"}}).map(&:name)
|
41
|
+
assert_equal ["Dollar Tree"], Store.search(json: {query: {match: {name: "Dollar Tree"}}}, load: false).map(&:name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_tokens
|
45
|
+
assert_equal ["dollar", "dollartre", "tree"], Product.searchkick_index.tokens("Dollar Tree")
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_tokens_analyzer
|
49
|
+
assert_equal ["dollar", "tree"], Product.searchkick_index.tokens("Dollar Tree", analyzer: "searchkick_search2")
|
50
|
+
end
|
51
|
+
|
38
52
|
def test_record_not_found
|
39
53
|
store_names ["Product A", "Product B"]
|
40
54
|
Product.where(name: "Product A").delete_all
|
data/test/sql_test.rb
CHANGED
@@ -33,6 +33,9 @@ class TestSql < Minitest::Unit::TestCase
|
|
33
33
|
assert_equal 2, products.limit_value
|
34
34
|
assert_equal 3, products.offset_value
|
35
35
|
assert_equal 3, products.offset
|
36
|
+
assert_equal 3, products.next_page
|
37
|
+
assert_equal 1, products.previous_page
|
38
|
+
assert_equal 1, products.prev_page
|
36
39
|
assert !products.first_page?
|
37
40
|
assert !products.last_page?
|
38
41
|
assert !products.empty?
|
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.7.
|
4
|
+
version: 0.7.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|