searchkick 2.3.1 → 2.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75ff5cc71f668744faa82225c281a7a636d2a774
4
- data.tar.gz: 3acfb221ce185cd58e4e56907a4b175556c1da53
3
+ metadata.gz: 8f081bd9d81b729d9bd3610154f95aeb355684f8
4
+ data.tar.gz: 88702f0b0417396e58697bd86919b92f716d470b
5
5
  SHA512:
6
- metadata.gz: c329befd3fdad5beb444e0db1c6dabc138ace231cb66f6f8e6abe880bd06fb949aca2ab59fbd135e2516c052d753e743aadac8269a4904fcfc968d7bd6ce626c
7
- data.tar.gz: 9943486403fc4a18e6fbf1a2ba3dcda15a85517afaacd8717ffa88dd2d47badb803c85eae9b0aac80b075e6afba2896404ffaeddb22d69c4546a0a8a1e579c4a
6
+ metadata.gz: bbebeb45ba1cff82280c6d0eab7f25ac42dbc6f22b600a23f583b4da36fa6e42a4ebab6d9e149087192ca765dd75aadd5eaa4fb3b5add5e8ce3613ff8f91a85f
7
+ data.tar.gz: d643fbc24e36968f2820b92875a28fbf215e68d752717d4d2fa8a8fcd7efa32009f03a4c8a983d88666ab7c18e2602e58895f6c36e4934b651c0a838ae4f45ab
data/.travis.yml CHANGED
@@ -20,7 +20,7 @@ gemfile:
20
20
  - test/gemfiles/mongoid5.gemfile
21
21
  - test/gemfiles/mongoid6.gemfile
22
22
  env:
23
- - ELASTICSEARCH_VERSION=5.4.0
23
+ - ELASTICSEARCH_VERSION=5.5.0
24
24
  jdk: oraclejdk8
25
25
  matrix:
26
26
  include:
@@ -33,3 +33,7 @@ matrix:
33
33
  - gemfile: Gemfile
34
34
  env: ELASTICSEARCH_VERSION=5.0.1
35
35
  jdk: oraclejdk8
36
+ allow_failures:
37
+ - gemfile: Gemfile
38
+ env: ELASTICSEARCH_VERSION=6.0.0-beta1
39
+ jdk: oraclejdk8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 2.3.2
2
+
3
+ - Added `_all` and `default_fields` options
4
+ - Added global `index_prefix` option
5
+ - Added `wait` option to async reindex
6
+ - Added `model_includes` option
7
+ - Added `missing` option for `boost_by`
8
+ - Raise error for `reindex_status` when Redis not configured
9
+ - Warn when incompatible options used with `body` option
10
+ - Fixed bug where `routing` and `type` options were silently ignored with `body` option
11
+ - Fixed `reindex(async: true)` for non-numeric primary keys in Postgres
12
+
1
13
  ## 2.3.1
2
14
 
3
15
  - Added support for `reindex(async: true)` for non-numeric primary keys
data/README.md CHANGED
@@ -1214,6 +1214,12 @@ And use:
1214
1214
  Searchkick.reindex_status(index_name)
1215
1215
  ```
1216
1216
 
1217
+ You can also have Searchkick wait for reindexing to complete [master]
1218
+
1219
+ ```ruby
1220
+ Searchkick.reindex(async: {wait: true})
1221
+ ```
1222
+
1217
1223
  You can use [ActiveJob::TrafficControl](https://github.com/nickelser/activejob-traffic_control) to control concurrency. Install the gem:
1218
1224
 
1219
1225
  ```ruby
@@ -1630,6 +1636,12 @@ Eager load associations
1630
1636
  Product.search "milk", includes: [:brand, :stores]
1631
1637
  ```
1632
1638
 
1639
+ Eager load different associations by model [master]
1640
+
1641
+ ```ruby
1642
+ Searchkick.search("*", index_name: [Product, Store], model_includes: {Product => [:store], Store => [:product]})
1643
+ ```
1644
+
1633
1645
  Turn off special characters
1634
1646
 
1635
1647
  ```ruby
data/lib/searchkick.rb CHANGED
@@ -8,6 +8,7 @@ require "searchkick/indexer"
8
8
  require "searchkick/reindex_queue"
9
9
  require "searchkick/results"
10
10
  require "searchkick/query"
11
+ require "searchkick/multi_search"
11
12
  require "searchkick/model"
12
13
  require "searchkick/tasks"
13
14
  require "searchkick/middleware"
@@ -36,7 +37,7 @@ module Searchkick
36
37
  class ImportError < Error; end
37
38
 
38
39
  class << self
39
- attr_accessor :search_method_name, :wordnet_path, :timeout, :models, :client_options, :redis, :index_suffix, :queue_name
40
+ attr_accessor :search_method_name, :wordnet_path, :timeout, :models, :client_options, :redis, :index_prefix, :index_suffix, :queue_name
40
41
  attr_writer :client, :env, :search_timeout
41
42
  attr_reader :aws_credentials
42
43
  end
@@ -101,14 +102,8 @@ module Searchkick
101
102
  end
102
103
  end
103
104
 
104
- def self.multi_search(queries)
105
- if queries.any?
106
- responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
107
- queries.each_with_index do |query, i|
108
- query.handle_response(responses[i])
109
- end
110
- end
111
- queries
105
+ def self.multi_search(queries, retry_misspellings: false)
106
+ Searchkick::MultiSearch.new(queries, retry_misspellings: retry_misspellings).perform
112
107
  end
113
108
 
114
109
  # callbacks
@@ -153,6 +148,8 @@ module Searchkick
153
148
  completed: batches_left == 0,
154
149
  batches_left: batches_left
155
150
  }
151
+ else
152
+ raise Searchkick::Error, "Redis not configured"
156
153
  end
157
154
  end
158
155
 
@@ -15,7 +15,13 @@ module Searchkick
15
15
  end
16
16
 
17
17
  def delete
18
- client.indices.delete index: name
18
+ if !Searchkick.server_below?("6.0.0-alpha1") && alias_exists?
19
+ # can't call delete directly on aliases in ES 6
20
+ indices = client.indices.get_alias(name: name).keys
21
+ client.indices.delete index: indices
22
+ else
23
+ client.indices.delete index: name
24
+ end
19
25
  end
20
26
 
21
27
  def exists?
@@ -228,7 +234,8 @@ module Searchkick
228
234
  end
229
235
 
230
236
  # check if alias exists
231
- if alias_exists?
237
+ alias_exists = alias_exists?
238
+ if alias_exists
232
239
  # import before promotion
233
240
  index.import_scope(scope, resume: resume, async: async, full: true) if import
234
241
 
@@ -246,6 +253,24 @@ module Searchkick
246
253
  end
247
254
 
248
255
  if async
256
+ if async.is_a?(Hash) && async[:wait]
257
+ puts "Created index: #{index.name}"
258
+ puts "Jobs queued. Waiting..."
259
+ loop do
260
+ sleep 3
261
+ status = Searchkick.reindex_status(index.name)
262
+ break if status[:completed]
263
+ puts "Batches left: #{status[:batches_left]}"
264
+ end
265
+ # already promoted if alias didn't exist
266
+ if alias_exists
267
+ puts "Jobs complete. Promoting..."
268
+ promote(index.name, update_refresh_interval: !refresh_interval.nil?)
269
+ end
270
+ clean_indices unless retain
271
+ puts "SUCCESS!"
272
+ end
273
+
249
274
  {index_name: index.name}
250
275
  else
251
276
  index.refresh
@@ -273,8 +298,8 @@ module Searchkick
273
298
 
274
299
  scope = scope.select("id").except(:includes, :preload) if async
275
300
 
276
- scope.find_in_batches batch_size: batch_size do |batch|
277
- import_or_update batch, method_name, async
301
+ scope.find_in_batches batch_size: batch_size do |items|
302
+ import_or_update items, method_name, async
278
303
  end
279
304
  else
280
305
  each_batch(scope) do |items|
@@ -290,7 +315,7 @@ module Searchkick
290
315
  # other
291
316
 
292
317
  def tokens(text, options = {})
293
- client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map { |t| t["token"] }
318
+ client.indices.analyze(body: {text: text}.merge(options), index: name)["tokens"].map { |t| t["token"] }
294
319
  end
295
320
 
296
321
  def klass_document_type(klass)
@@ -421,7 +446,13 @@ module Searchkick
421
446
  # TODO expire Redis key
422
447
  primary_key = scope.primary_key
423
448
 
424
- starting_id = scope.minimum(primary_key)
449
+ starting_id =
450
+ begin
451
+ scope.minimum(primary_key)
452
+ rescue ActiveRecord::StatementInvalid
453
+ false
454
+ end
455
+
425
456
  if starting_id.nil?
426
457
  # no records, do nothing
427
458
  elsif starting_id.is_a?(Numeric)
@@ -11,6 +11,7 @@ module Searchkick
11
11
  else
12
12
  below22 = Searchkick.server_below?("2.2.0")
13
13
  below50 = Searchkick.server_below?("5.0.0-alpha1")
14
+ below60 = Searchkick.server_below?("6.0.0-alpha1")
14
15
  default_type = below50 ? "string" : "text"
15
16
  default_analyzer = :searchkick_index
16
17
  keyword_mapping =
@@ -25,6 +26,10 @@ module Searchkick
25
26
  }
26
27
  end
27
28
 
29
+ all = options.key?(:_all) ? options[:_all] : below60
30
+ index_true_value = below50 ? "analyzed" : true
31
+ index_false_value = below50 ? "no" : false
32
+
28
33
  keyword_mapping[:ignore_above] = (options[:ignore_above] || 30000) unless below22
29
34
 
30
35
  settings = {
@@ -178,7 +183,7 @@ module Searchkick
178
183
  # - Only apply the synonym expansion at index time
179
184
  # - Don't have the synonym filter applied search
180
185
  # - Use directional synonyms where appropriate. You want to make sure that you're not injecting terms that are too general.
181
- settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_synonym")
186
+ settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_synonym") if below60
182
187
  settings[:analysis][:analyzer][default_analyzer][:filter] << "searchkick_synonym"
183
188
 
184
189
  %w(word_start word_middle word_end).each do |type|
@@ -229,13 +234,13 @@ module Searchkick
229
234
 
230
235
  mapping_options[:searchable].delete("_all")
231
236
 
232
- analyzed_field_options = {type: default_type, index: "analyzed", analyzer: default_analyzer}
237
+ analyzed_field_options = {type: default_type, index: index_true_value, analyzer: default_analyzer}
233
238
 
234
239
  mapping_options.values.flatten.uniq.each do |field|
235
240
  fields = {}
236
241
 
237
242
  if options.key?(:filterable) && !mapping_options[:filterable].include?(field)
238
- fields[field] = {type: default_type, index: "no"}
243
+ fields[field] = {type: default_type, index: index_false_value}
239
244
  else
240
245
  fields[field] = keyword_mapping
241
246
  end
@@ -251,7 +256,7 @@ module Searchkick
251
256
 
252
257
  mapping_options.except(:highlight, :searchable, :filterable, :word).each do |type, f|
253
258
  if options[:match] == type || f.include?(field)
254
- fields[type] = {type: default_type, index: "analyzed", analyzer: "searchkick_#{type}_index"}
259
+ fields[type] = {type: default_type, index: index_true_value, analyzer: "searchkick_#{type}_index"}
255
260
  end
256
261
  end
257
262
  end
@@ -283,16 +288,20 @@ module Searchkick
283
288
  # http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
284
289
  # however, we can include the not_analyzed field in _all
285
290
  # and the _all index analyzer will take care of it
286
- "{name}" => keyword_mapping.merge(include_in_all: !options[:searchable])
291
+ "{name}" => keyword_mapping
287
292
  }
288
293
 
294
+ if below60 && all
295
+ dynamic_fields["{name}"][:include_in_all] = !options[:searchable]
296
+ end
297
+
289
298
  if options.key?(:filterable)
290
- dynamic_fields["{name}"] = {type: default_type, index: "no"}
299
+ dynamic_fields["{name}"] = {type: default_type, index: index_false_value}
291
300
  end
292
301
 
293
302
  unless options[:searchable]
294
303
  if options[:match] && options[:match] != :word
295
- dynamic_fields[options[:match]] = {type: default_type, index: "analyzed", analyzer: "searchkick_#{options[:match]}_index"}
304
+ dynamic_fields[options[:match]] = {type: default_type, index: index_true_value, analyzer: "searchkick_#{options[:match]}_index"}
296
305
  end
297
306
 
298
307
  if word
@@ -303,11 +312,8 @@ module Searchkick
303
312
  # http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
304
313
  multi_field = dynamic_fields["{name}"].merge(fields: dynamic_fields.except("{name}"))
305
314
 
306
- all_enabled = !options[:searchable] || options[:searchable].to_a.map(&:to_s).include?("_all")
307
-
308
315
  mappings = {
309
316
  _default_: {
310
- _all: all_enabled ? analyzed_field_options : {enabled: false},
311
317
  properties: mapping,
312
318
  _routing: routing,
313
319
  # https://gist.github.com/kimchy/2898285
@@ -321,7 +327,14 @@ module Searchkick
321
327
  }
322
328
  ]
323
329
  }
324
- }.deep_merge(options[:mappings] || {})
330
+ }
331
+
332
+ if below60
333
+ all_enabled = all && (!options[:searchable] || options[:searchable].to_a.map(&:to_s).include?("_all"))
334
+ mappings[:_default_][:_all] = all_enabled ? analyzed_field_options : {enabled: false}
335
+ end
336
+
337
+ mappings = mappings.deep_merge(options[:mappings] || {})
325
338
  end
326
339
 
327
340
  {
@@ -129,7 +129,7 @@ module Searchkick
129
129
  end
130
130
 
131
131
  module SearchkickWithInstrumentation
132
- def multi_search(searches)
132
+ def multi_search(searches, **options)
133
133
  event = {
134
134
  name: "Multi Search",
135
135
  body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
@@ -1,7 +1,7 @@
1
1
  module Searchkick
2
2
  module Model
3
3
  def searchkick(**options)
4
- unknown_keywords = options.keys - [:batch_size, :callbacks, :conversions,
4
+ unknown_keywords = options.keys - [:_all, :batch_size, :callbacks, :conversions, :default_fields,
5
5
  :filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :language,
6
6
  :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :settings, :similarity,
7
7
  :special_characters, :stem_conversions, :suggest, :synonyms, :text_end,
@@ -22,7 +22,7 @@ module Searchkick
22
22
  class_variable_set :@@searchkick_callbacks, callbacks
23
23
  class_variable_set :@@searchkick_index, options[:index_name] ||
24
24
  (options[:index_prefix].respond_to?(:call) && proc { [options[:index_prefix].call, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_") }) ||
25
- [options[:index_prefix], model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_")
25
+ [options.key?(:index_prefix) ? options[:index_prefix] : Searchkick.index_prefix, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_")
26
26
 
27
27
  class << self
28
28
  def searchkick_search(term = "*", **options, &block)
@@ -0,0 +1,42 @@
1
+ module Searchkick
2
+ class MultiSearch
3
+ attr_reader :queries
4
+
5
+ def initialize(queries, retry_misspellings: false)
6
+ @queries = queries
7
+ @retry_misspellings = retry_misspellings
8
+ end
9
+
10
+ def perform
11
+ if queries.any?
12
+ perform_search(queries, retry_misspellings: @retry_misspellings)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def perform_search(queries, retry_misspellings: true)
19
+ responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
20
+
21
+ retry_queries = []
22
+ queries.each_with_index do |query, i|
23
+ if retry_misspellings && query.retry_misspellings?(responses[i])
24
+ query.send(:prepare) # okay, since we don't want to expose this method outside Searchkick
25
+ retry_queries << query
26
+ else
27
+ query.handle_response(responses[i])
28
+ end
29
+ end
30
+
31
+ if retry_misspellings && retry_queries.any?
32
+ perform_search(retry_queries, retry_misspellings: false)
33
+ end
34
+
35
+ queries
36
+ end
37
+
38
+ def client
39
+ Searchkick.client
40
+ end
41
+ end
42
+ end
@@ -18,7 +18,7 @@ module Searchkick
18
18
  unknown_keywords = options.keys - [:aggs, :body, :body_options, :boost,
19
19
  :boost_by, :boost_by_distance, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :execute, :explain,
20
20
  :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
21
- :match, :misspellings, :offset, :operator, :order, :padding, :page, :per_page, :profile,
21
+ :match, :misspellings, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
22
22
  :request_params, :routing, :select, :similar, :smart_aggs, :suggest, :track, :type, :where]
23
23
  raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
24
24
 
@@ -79,7 +79,7 @@ module Searchkick
79
79
  @execute ||= begin
80
80
  begin
81
81
  response = execute_search
82
- if @misspellings_below && response["hits"]["total"] < @misspellings_below
82
+ if retry_misspellings?(response)
83
83
  prepare
84
84
  response = execute_search
85
85
  end
@@ -108,6 +108,7 @@ module Searchkick
108
108
  padding: @padding,
109
109
  load: @load,
110
110
  includes: options[:includes],
111
+ model_includes: options[:model_includes],
111
112
  json: !@json.nil?,
112
113
  match_suffix: @match_suffix,
113
114
  highlighted_fields: @highlighted_fields || [],
@@ -159,6 +160,10 @@ module Searchkick
159
160
  @execute = Searchkick::Results.new(searchkick_klass, response, opts)
160
161
  end
161
162
 
163
+ def retry_misspellings?(response)
164
+ @misspellings_below && response["hits"]["total"] < @misspellings_below
165
+ end
166
+
162
167
  private
163
168
 
164
169
  def handle_error(e)
@@ -218,6 +223,11 @@ module Searchkick
218
223
 
219
224
  @json = options[:body]
220
225
  if @json
226
+ ignored_options = options.keys & [:aggs, :boost,
227
+ :boost_by, :boost_by_distance, :boost_where, :conversions, :conversions_term, :exclude, :explain,
228
+ :fields, :highlight, :indices_boost, :limit, :match, :misspellings, :offset, :operator, :order,
229
+ :padding, :page, :per_page, :select, :smart_aggs, :suggest, :where]
230
+ warn "The body option replaces the entire body, so the following options are ignored: #{ignored_options.join(", ")}" if ignored_options.any?
221
231
  payload = @json
222
232
  else
223
233
  if options[:similar]
@@ -468,15 +478,16 @@ module Searchkick
468
478
  elsif load
469
479
  payload[:_source] = false
470
480
  end
481
+ end
471
482
 
472
- if options[:type] || (klass != searchkick_klass && searchkick_index)
473
- @type = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v) }
474
- end
475
-
476
- # routing
477
- @routing = options[:routing] if options[:routing]
483
+ # type
484
+ if options[:type] || (klass != searchkick_klass && searchkick_index)
485
+ @type = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v) }
478
486
  end
479
487
 
488
+ # routing
489
+ @routing = options[:routing] if options[:routing]
490
+
480
491
  # merge more body options
481
492
  payload = payload.deep_merge(options[:body_options]) if options[:body_options]
482
493
 
@@ -489,7 +500,8 @@ module Searchkick
489
500
 
490
501
  def set_fields
491
502
  boost_fields = {}
492
- fields = options[:fields] || searchkick_options[:searchable]
503
+ fields = options[:fields] || searchkick_options[:default_fields] || searchkick_options[:searchable]
504
+ all = searchkick_options.key?(:_all) ? searchkick_options[:_all] : below60?
493
505
  default_match = options[:match] || searchkick_options[:match] || :word
494
506
  fields =
495
507
  if fields
@@ -500,12 +512,12 @@ module Searchkick
500
512
  boost_fields[field] = boost.to_f if boost
501
513
  field
502
514
  end
503
- elsif default_match == :word
515
+ elsif all && default_match == :word
504
516
  ["_all"]
505
- elsif default_match == :phrase
517
+ elsif all && default_match == :phrase
506
518
  ["_all.phrase"]
507
519
  else
508
- raise ArgumentError, "Must specify fields"
520
+ raise ArgumentError, "Must specify fields to search"
509
521
  end
510
522
  [boost_fields, fields]
511
523
  end
@@ -830,7 +842,7 @@ module Searchkick
830
842
  if value.any?(&:nil?)
831
843
  {bool: {should: [term_filters(field, nil), term_filters(field, value.compact)]}}
832
844
  else
833
- {in: {field => value}}
845
+ {terms: {field => value}}
834
846
  end
835
847
  elsif value.nil?
836
848
  {bool: {must_not: {exists: {field: field}}}}
@@ -871,13 +883,21 @@ module Searchkick
871
883
  }
872
884
  }
873
885
 
874
- {
875
- filter: {
886
+ if value[:missing]
887
+ if below50?
888
+ raise ArgumentError, "The missing option for boost_by is not supported in Elasticsearch < 5"
889
+ else
890
+ script_score[:field_value_factor][:missing] = value[:missing].to_f
891
+ end
892
+ else
893
+ script_score[:filter] = {
876
894
  exists: {
877
895
  field: field
878
896
  }
879
897
  }
880
- }.merge(script_score)
898
+ end
899
+
900
+ script_score
881
901
  end
882
902
  end
883
903
 
@@ -905,5 +925,9 @@ module Searchkick
905
925
  def below50?
906
926
  Searchkick.server_below?("5.0.0-alpha1")
907
927
  end
928
+
929
+ def below60?
930
+ Searchkick.server_below?("6.0.0-alpha1")
931
+ end
908
932
  end
909
933
  end
@@ -198,23 +198,36 @@ module Searchkick
198
198
 
199
199
  def results_query(records, hits)
200
200
  ids = hits.map { |hit| hit["_id"] }
201
+ if options[:includes] || options[:model_includes]
202
+ included_relations = []
203
+ combine_includes(included_relations, options[:includes])
204
+ combine_includes(included_relations, options[:model_includes][records]) if options[:model_includes]
201
205
 
202
- if options[:includes]
203
206
  records =
204
207
  if defined?(NoBrainer::Document) && records < NoBrainer::Document
205
208
  if Gem.loaded_specs["nobrainer"].version >= Gem::Version.new("0.21")
206
- records.eager_load(options[:includes])
209
+ records.eager_load(included_relations)
207
210
  else
208
- records.preload(options[:includes])
211
+ records.preload(included_relations)
209
212
  end
210
213
  else
211
- records.includes(options[:includes])
214
+ records.includes(included_relations)
212
215
  end
213
216
  end
214
217
 
215
218
  Searchkick.load_records(records, ids)
216
219
  end
217
220
 
221
+ def combine_includes(result, inc)
222
+ if inc
223
+ if inc.is_a?(Array)
224
+ result.concat(inc)
225
+ else
226
+ result << inc
227
+ end
228
+ end
229
+ end
230
+
218
231
  def base_field(k)
219
232
  k.sub(/\.(analyzed|word_start|word_middle|word_end|text_start|text_middle|text_end|exact)\z/, "")
220
233
  end
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "2.3.1"
2
+ VERSION = "2.3.2"
3
3
  end
data/test/boost_test.rb CHANGED
@@ -116,6 +116,21 @@ class BoostTest < Minitest::Test
116
116
  assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost_by: {orders_count: {factor: 10}}
117
117
  end
118
118
 
119
+ def test_boost_by_missing
120
+ store [
121
+ {name: "Tomato A"},
122
+ {name: "Tomato B", orders_count: 10},
123
+ ]
124
+
125
+ if elasticsearch_below50?
126
+ assert_raises(ArgumentError) do
127
+ assert_order "tomato", ["Tomato A", "Tomato B"], boost_by: {orders_count: {missing: 100}}
128
+ end
129
+ else
130
+ assert_order "tomato", ["Tomato A", "Tomato B"], boost_by: {orders_count: {missing: 100}}
131
+ end
132
+ end
133
+
119
134
  def test_boost_by_boost_mode_multiply
120
135
  store [
121
136
  {name: "Tomato A", found_rate: 0.9},
@@ -108,7 +108,7 @@ class GeoShapeTest < Minitest::Test
108
108
  geo_shape: {
109
109
  type: "envelope",
110
110
  relation: "within",
111
- coordinates: [[20,50], [50,20]]
111
+ coordinates: [[20, 50], [50, 20]]
112
112
  }
113
113
  }
114
114
  }
@@ -116,6 +116,9 @@ class GeoShapeTest < Minitest::Test
116
116
  end
117
117
 
118
118
  def test_search_math
119
+ # TODO find out why this is failing
120
+ skip unless elasticsearch_below60?
121
+
119
122
  assert_search "witch", ["Region A"], {
120
123
  where: {
121
124
  territory: {
@@ -32,7 +32,7 @@ class HighlightTest < Minitest::Test
32
32
 
33
33
  def test_field_options
34
34
  store_names ["Two Door Cinema Club are a Northern Irish indie rock band"]
35
- fragment_size = ENV["MATCH"] == "word_start" ? 26 : 20
35
+ fragment_size = ENV["MATCH"] == "word_start" ? 26 : 21
36
36
  assert_equal "Two Door <em>Cinema</em> Club are", Product.search("cinema", fields: [:name], highlight: {fields: {name: {fragment_size: fragment_size}}}).first.search_highlights[:name]
37
37
  end
38
38
 
data/test/index_test.rb CHANGED
@@ -59,6 +59,10 @@ class IndexTest < Minitest::Test
59
59
  assert_equal ["Dollar Tree"], Store.search(body: {query: {match: {name: "Dollar Tree"}}}, load: false).map(&:name)
60
60
  end
61
61
 
62
+ def test_body_warning
63
+ assert_output(nil, "The body option replaces the entire body, so the following options are ignored: where\n") { Store.search(body: {query: {match: {name: "dollar"}}}, where: {id: 1}) }
64
+ end
65
+
62
66
  def test_block
63
67
  store_names ["Dollar Tree"]
64
68
  products =
@@ -143,7 +147,11 @@ class IndexTest < Minitest::Test
143
147
  store [{name: "Product A", text: large_value}], Region
144
148
  assert_search "product", ["Product A"], {}, Region
145
149
  assert_search "hello", ["Product A"], {fields: [:name, :text]}, Region
146
- assert_search "hello", ["Product A"], {}, Region
150
+
151
+ # needs fields for ES 6
152
+ if elasticsearch_below60?
153
+ assert_search "hello", ["Product A"], {}, Region
154
+ end
147
155
  end
148
156
 
149
157
  def test_very_large_value
@@ -19,4 +19,18 @@ class MultiSearchTest < Minitest::Test
19
19
  assert !products.error
20
20
  assert stores.error
21
21
  end
22
+
23
+ def test_misspellings_below_unmet
24
+ store_names ["abc", "abd", "aee"]
25
+ products = Product.search("abc", misspellings: {below: 2}, execute: false)
26
+ Searchkick.multi_search([products])
27
+ assert_equal ["abc"], products.map(&:name)
28
+ end
29
+
30
+ def test_misspellings_below_unmet_retry
31
+ store_names ["abc", "abd", "aee"]
32
+ products = Product.search("abc", misspellings: {below: 2}, execute: false)
33
+ Searchkick.multi_search([products], retry_misspellings: true)
34
+ assert_equal ["abc", "abd"], products.map(&:name)
35
+ end
22
36
  end
data/test/routing_test.rb CHANGED
@@ -13,11 +13,11 @@ class RoutingTest < Minitest::Test
13
13
 
14
14
  def test_routing_correct_node
15
15
  store_names ["Dollar Tree"], Store
16
- assert_search "dollar", ["Dollar Tree"], {routing: "Dollar Tree"}, Store
16
+ assert_search "*", ["Dollar Tree"], {routing: "Dollar Tree"}, Store
17
17
  end
18
18
 
19
19
  def test_routing_incorrect_node
20
20
  store_names ["Dollar Tree"], Store
21
- assert_search "dollar", ["Dollar Tree"], {routing: "Boom"}, Store
21
+ assert_search "*", ["Dollar Tree"], {routing: "Boom"}, Store
22
22
  end
23
23
  end
data/test/sql_test.rb CHANGED
@@ -195,4 +195,20 @@ class SqlTest < Minitest::Test
195
195
  store_names ["Product A"]
196
196
  assert Product.search("product", includes: [:store]).first.association(:store).loaded?
197
197
  end
198
+
199
+ def test_model_includes
200
+ skip unless defined?(ActiveRecord)
201
+
202
+ store_names ["Product A"]
203
+ store_names ["Store A"], Store
204
+
205
+ associations = {Product => [:store], Store => [:products]}
206
+ result = Searchkick.search("*", index_name: [Product, Store], model_includes: associations)
207
+
208
+ assert_equal 2, result.length
209
+
210
+ result.group_by(&:class).each_pair do |klass, records|
211
+ assert records.first.association(associations[klass].first).loaded?
212
+ end
213
+ end
198
214
  end
data/test/suggest_test.rb CHANGED
@@ -69,7 +69,7 @@ class SuggestTest < Minitest::Test
69
69
 
70
70
  def test_multiple_models
71
71
  store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
72
- assert_equal "how big is a tiger shark", Searchkick.search("How Big is a Tigre Shar", suggest: [:name]).suggestions.first
72
+ assert_equal "how big is a tiger shark", Searchkick.search("How Big is a Tigre Shar", suggest: [:name], fields: [:name]).suggestions.first
73
73
  end
74
74
 
75
75
  def test_multiple_models_no_fields
data/test/test_helper.rb CHANGED
@@ -42,6 +42,10 @@ def elasticsearch_below50?
42
42
  Searchkick.server_below?("5.0.0-alpha1")
43
43
  end
44
44
 
45
+ def elasticsearch_below60?
46
+ Searchkick.server_below?("6.0.0-alpha1")
47
+ end
48
+
45
49
  def elasticsearch_below22?
46
50
  Searchkick.server_below?("2.2.0")
47
51
  end
@@ -444,6 +448,7 @@ end
444
448
 
445
449
  class Store
446
450
  searchkick \
451
+ default_fields: elasticsearch_below60? ? nil : [:name],
447
452
  routing: true,
448
453
  merge_mappings: true,
449
454
  mappings: {
@@ -465,6 +470,7 @@ end
465
470
 
466
471
  class Region
467
472
  searchkick \
473
+ default_fields: elasticsearch_below60? ? nil : [:name],
468
474
  geo_shape: {
469
475
  territory: {tree: "quadtree", precision: "10km"}
470
476
  }
@@ -482,6 +488,7 @@ end
482
488
 
483
489
  class Speaker
484
490
  searchkick \
491
+ default_fields: elasticsearch_below60? ? nil : [:name],
485
492
  conversions: ["conversions_a", "conversions_b"]
486
493
 
487
494
  attr_accessor :conversions_a, :conversions_b, :aisle
@@ -497,6 +504,7 @@ end
497
504
 
498
505
  class Animal
499
506
  searchkick \
507
+ default_fields: elasticsearch_below60? ? nil : [:name],
500
508
  text_start: [:name],
501
509
  suggest: [:name],
502
510
  index_name: -> { "#{name.tableize}-#{Date.today.year}#{Searchkick.index_suffix}" },
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: 2.3.1
4
+ version: 2.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-07 00:00:00.000000000 Z
11
+ date: 2017-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -118,6 +118,7 @@ files:
118
118
  - lib/searchkick/logging.rb
119
119
  - lib/searchkick/middleware.rb
120
120
  - lib/searchkick/model.rb
121
+ - lib/searchkick/multi_search.rb
121
122
  - lib/searchkick/process_batch_job.rb
122
123
  - lib/searchkick/process_queue_job.rb
123
124
  - lib/searchkick/query.rb
@@ -194,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
195
  version: '0'
195
196
  requirements: []
196
197
  rubyforge_project:
197
- rubygems_version: 2.6.11
198
+ rubygems_version: 2.6.13
198
199
  signing_key:
199
200
  specification_version: 4
200
201
  summary: Searchkick learns what your users are looking for. As more people search,