searchkick 0.7.8 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bb8ada9111159102b2327b5344afe0876aac137d
4
- data.tar.gz: 3e3e655a723d06f6b741b0938d3fde6d02d5c7ab
3
+ metadata.gz: e2bc8db79f3c60a5694ccd3400713d0dc8788ffd
4
+ data.tar.gz: 163dfde8a0f72d3f9584ad7e6f94b6370bafceb3
5
5
  SHA512:
6
- metadata.gz: fdb99efc9149d644dd126456c9ab2e753a6f67a77feb90e07ad20dcbea75dd7152705136a2e30cfd0fd9e1a38a0ba35b1f05d0b8dd6c986ba248aa90a82126b2
7
- data.tar.gz: 04a2c3229e5f40a866fa8cc2aed8832d361d680ef9a88fe75d1e3b8dc72875524961056f2f00bf2edd7bcd07989864118367547006198588b5f3af3e0e81ee96
6
+ metadata.gz: 83f9f13181e205843a72d3539b4ebba790c98f3abdbb9f1be1a3bfda539eb415304bd8e9eaf9acea062f64f6dbbc227db0892cd76623178d0ab20783119a91d4
7
+ data.tar.gz: 2123d48c3269eb517108f8864740bb2d1cfdc3e0130ed99c161a51d09d97f24799625fc83896912ce4351cfd678434665cefcf176935c2b08f5b5006a23d031a
@@ -1,3 +1,11 @@
1
+ ## 0.7.9
2
+
3
+ - Added `tokens` method
4
+ - Added `json` option
5
+ - Added exact matches
6
+ - Added `prev_page` for Kaminari pagination
7
+ - Added `import` option to reindex
8
+
1
9
  ## 0.7.8
2
10
 
3
11
  - Added `boost_by` and `boost_where` options
data/Gemfile CHANGED
@@ -4,6 +4,6 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem "sqlite3"
7
- gem "activerecord"
7
+ gem "activerecord", ">= 3.1"
8
8
  # gem "activerecord", "~> 3.2.0"
9
9
  # gem "activerecord", "~> 3.1.0"
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 "searchkick"
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
- user_ids: orders.pluck(:user_id) # boost this product for these users
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: {user_id: 8}
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`.
@@ -65,6 +65,10 @@ module Searchkick
65
65
  end
66
66
  end
67
67
 
68
+ def tokens(text, options = {})
69
+ client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map{|t| t["token"] }
70
+ end
71
+
68
72
  protected
69
73
 
70
74
  def client
@@ -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[:query]
54
- payload = options[:query]
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[:autocomplete]
60
+ if options[:query]
61
+ payload = options[:query]
62
+ elsif options[:similar]
71
63
  payload = {
72
- multi_match: {
64
+ more_like_this: {
73
65
  fields: fields,
74
- query: term,
75
- analyzer: "searchkick_autocomplete_search"
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
- queries = []
80
- fields.each do |field|
81
- factor = boost_fields[field] || 1
82
- shared_options = {
83
- fields: [field],
84
- query: term,
85
- use_dis_max: false,
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
- if field == "_all" or field.end_with?(".analyzed")
90
- shared_options[:cutoff_frequency] = 0.001 unless operator == "and"
91
- queries.concat [
92
- {multi_match: shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search")},
93
- {multi_match: shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search2")}
94
- ]
95
- if options[:misspellings] != false
96
- distance = (options[:misspellings].is_a?(Hash) && options[:misspellings][:distance]) || 1
97
- queries.concat [
98
- {multi_match: shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search")},
99
- {multi_match: shared_options.merge(fuzziness: distance, max_expansions: 3, analyzer: "searchkick_search2")}
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
- else
103
- analyzer = field.match(/\.word_(start|middle|end)\z/) ? "searchkick_word_search" : "searchkick_autocomplete_search"
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
- payload = {
109
- dis_max: {
110
- queries: queries
121
+ payload = {
122
+ dis_max: {
123
+ queries: queries
124
+ }
111
125
  }
112
- }
113
- end
126
+ end
114
127
 
115
- if conversions_field and options[:conversions] != false
116
- # wrap payload in a bool query
117
- payload = {
118
- bool: {
119
- must: payload,
120
- should: {
121
- nested: {
122
- path: conversions_field,
123
- score_mode: "total",
124
- query: {
125
- function_score: {
126
- boost_mode: "replace",
127
- query: {
128
- match: {
129
- query: term
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
- custom_filters = []
157
+ custom_filters = []
145
158
 
146
- boost_by = options[:boost_by] || {}
147
- if boost_by.is_a?(Array)
148
- boost_by = Hash[ boost_by.map{|f| [f, {factor: 1}] } ]
149
- end
150
- if options[:boost]
151
- boost_by[options[:boost]] = {factor: 1}
152
- end
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
- boost_by.each do |field, value|
155
- custom_filters << {
156
- filter: {
157
- exists: {
158
- field: field
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
- if custom_filters.any?
189
- payload = {
190
- function_score: {
191
- functions: custom_filters,
192
- query: payload,
193
- score_mode: "sum"
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
- # order
206
- if options[:order]
207
- order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
208
- payload[:sort] = Hash[ order.map{|k, v| [k.to_s == "id" ? :_id : k, v] } ]
209
- end
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
- # filters
212
- filters = where_filters(options[:where])
213
- if filters.any?
214
- payload[:filter] = {
215
- and: filters
211
+ payload = {
212
+ query: payload,
213
+ size: per_page,
214
+ from: offset
216
215
  }
217
- end
216
+ payload[:explain] = options[:explain] if options[:explain]
218
217
 
219
- # facets
220
- facet_limits = {}
221
- if options[:facets]
222
- facets = options[:facets] || {}
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
- payload[:facets] = {}
228
- facets.each do |field, facet_options|
229
- # ask for extra facets due to
230
- # https://github.com/elasticsearch/elasticsearch/issues/1305
231
- size = facet_options[:limit] ? facet_options[:limit] + 150 : 100000
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
- if facet_options[:ranges]
234
- payload[:facets][field] = {
235
- range: {
236
- field.to_sym => facet_options[:ranges]
245
+ if facet_options[:ranges]
246
+ payload[:facets][field] = {
247
+ range: {
248
+ field.to_sym => facet_options[:ranges]
249
+ }
237
250
  }
238
- }
239
- elsif facet_options[:stats]
240
- payload[:facets][field] = {
241
- terms_stats: {
242
- key_field: field,
243
- value_script: "doc.score",
244
- size: size
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
- else
248
- payload[:facets][field] = {
249
- terms: {
250
- field: field,
251
- size: size
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
- facet_limits[field] = facet_options[:limit] if facet_options[:limit]
268
+ facet_limits[field] = facet_options[:limit] if facet_options[:limit]
257
269
 
258
- # offset is not possible
259
- # http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
270
+ # offset is not possible
271
+ # http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
260
272
 
261
- facet_options.deep_merge!(where: options[:where].reject{|k| k == field}) if options[:smart_facets] == true
262
- facet_filters = where_filters(facet_options[:where])
263
- if facet_filters.any?
264
- payload[:facets][field][:facet_filter] = {
265
- and: {
266
- filters: facet_filters
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
- # suggestions
274
- if options[:suggest]
275
- suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
285
+ # suggestions
286
+ if options[:suggest]
287
+ suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
276
288
 
277
- # intersection
278
- if options[:fields]
279
- suggest_fields = suggest_fields & options[:fields].map{|v| (v.is_a?(Hash) ? v.keys.first : v).to_s }
280
- end
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
- if suggest_fields.any?
283
- payload[:suggest] = {text: term}
284
- suggest_fields.each do |field|
285
- payload[:suggest][field] = {
286
- phrase: {
287
- field: "#{field}.suggest"
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
- # highlight
295
- if options[:highlight]
296
- payload[:highlight] = {
297
- fields: Hash[ fields.map{|f| [f, {}] } ]
298
- }
299
- if options[:highlight].is_a?(Hash) and tag = options[:highlight][:tag]
300
- payload[:highlight][:pre_tags] = [tag]
301
- payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
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
- # model and eagar loading
306
- load = options[:load].nil? ? true : options[:load]
307
-
308
- # An empty array will cause only the _id and _type for each hit to be returned
309
- # http://www.elasticsearch.org/guide/reference/api/search/fields/
310
- if load
311
- payload[:fields] = []
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
- if options[:type] or klass != searchkick_klass
317
- @type = [options[:type] || klass].flatten.map{|v| searchkick_index.klass_document_type(v) }
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
@@ -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
- searchkick_import(index) # import before swap
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
- searchkick_import(index) # import after swap
30
+
31
+ # import after swap
32
+ searchkick_import(index) unless skip_import
28
33
  end
29
34
 
30
35
  index.refresh
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "0.7.8"
2
+ VERSION = "0.7.9"
3
3
  end
@@ -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
@@ -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
@@ -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
@@ -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.8
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-06-22 00:00:00.000000000 Z
11
+ date: 2014-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel