searchkick-hooopo 2.3.3 → 2.3.4

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: b3c84b55217833c53ab9e6251eb619b26759268d
4
- data.tar.gz: 718ae414710c5fb1332e12b173824be81a5e7215
3
+ metadata.gz: b0a583527f94e9f2ff5bd516fa14954d2929454a
4
+ data.tar.gz: e87d1df59be8d7d971d62e3e678743c36bdd15f4
5
5
  SHA512:
6
- metadata.gz: 5af2cb7f2798f4992a428577ed6f8e0e6780e8bf8a700992b5f125eb9ecadcf98220a9075a9b1a54c4ff73869e8220ee842ce3fa805b6334da80408206e13c81
7
- data.tar.gz: be5e27625bc3363d3fc6e9765b6ffbbcb7843b38ad2413b3a5f4f626773c241a565b004f336ad062049ffccd14eb5594f5a276f4ca6e634f0f7d84207d794520
6
+ metadata.gz: 04ced7e3dbbce7f762ae215dea74e2eab5365f09edac3adcf6f4231c1fde5f94964ce1eb1c6498758cc45dbdfc5cca51e49afa7d5a6862e3ca9fb3d045f0349f
7
+ data.tar.gz: fae7c443309dd3d6d499b70d4f80870faf789b0197e548d0224f514917dda9a5b073c8e03e53d203175a4edaba9379568ba1019947f80ce1761cad301c5dbcb9
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,17 @@
1
+ ## 2.3.2 [unreleased]
2
+
3
+ - Added `_all` and `default_fields` options
4
+ - Added global `index_prefix` option
5
+ - Added `wait` option to async reindex
6
+ - Raise error for `reindex_status` when Redis not configured
7
+
8
+ ## 2.3.1
9
+
10
+ - Added support for `reindex(async: true)` for non-numeric primary keys
11
+ - Added `conversions_term` option
12
+ - Added support for passing fields to `suggest` option
13
+ - Fixed `page_view_entries` for Kaminari
14
+
1
15
  ## 2.3.0
2
16
 
3
17
  - Fixed analyzer on dynamically mapped fields
data/Gemfile CHANGED
@@ -10,3 +10,7 @@ gem "typhoeus"
10
10
  gem "activejob"
11
11
  gem "redis"
12
12
  gem "connection_pool"
13
+
14
+ # kaminari
15
+ gem "actionpack"
16
+ gem "kaminari"
data/README.md CHANGED
@@ -1177,14 +1177,16 @@ end
1177
1177
 
1178
1178
  ### Filterable Fields
1179
1179
 
1180
- By default, all fields are filterable (can be used in `where` option). Speed up indexing and reduce index size by only making some fields filterable.
1180
+ By default, all string fields are filterable (can be used in `where` option). Speed up indexing and reduce index size by only making some fields filterable.
1181
1181
 
1182
1182
  ```ruby
1183
1183
  class Product < ActiveRecord::Base
1184
- searchkick filterable: [:store_id]
1184
+ searchkick filterable: [:brand]
1185
1185
  end
1186
1186
  ```
1187
1187
 
1188
+ **Note:** Non-string fields will always be filterable and should not be passed to this option.
1189
+
1188
1190
  ### Parallel Reindexing
1189
1191
 
1190
1192
  For large data sets, you can use background jobs to parallelize reindexing.
@@ -1212,6 +1214,12 @@ And use:
1212
1214
  Searchkick.reindex_status(index_name)
1213
1215
  ```
1214
1216
 
1217
+ You can also have Searchkick wait for reindexing to complete [master]
1218
+
1219
+ ```ruby
1220
+ Searchkick.reindex(async: {wait: true})
1221
+ ```
1222
+
1215
1223
  You can use [ActiveJob::TrafficControl](https://github.com/nickelser/activejob-traffic_control) to control concurrency. Install the gem:
1216
1224
 
1217
1225
  ```ruby
@@ -1563,6 +1571,12 @@ class Product < ActiveRecord::Base
1563
1571
  end
1564
1572
  ```
1565
1573
 
1574
+ Use a different term for boosting by conversions
1575
+
1576
+ ```ruby
1577
+ Product.search("banana", conversions_term: "organic banana")
1578
+ ```
1579
+
1566
1580
  Multiple conversion fields
1567
1581
 
1568
1582
  ```ruby
@@ -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
@@ -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)
@@ -420,14 +445,25 @@ module Searchkick
420
445
  if scope.respond_to?(:primary_key)
421
446
  # TODO expire Redis key
422
447
  primary_key = scope.primary_key
423
- starting_id = scope.minimum(primary_key) || 0
424
- max_id = scope.maximum(primary_key) || 0
425
- batches_count = ((max_id - starting_id + 1) / batch_size.to_f).ceil
426
-
427
- batches_count.times do |i|
428
- batch_id = i + 1
429
- min_id = starting_id + (i * batch_size)
430
- bulk_reindex_job scope, batch_id, min_id: min_id, max_id: min_id + batch_size - 1
448
+
449
+ starting_id = scope.minimum(primary_key)
450
+ if starting_id.nil?
451
+ # no records, do nothing
452
+ elsif starting_id.is_a?(Numeric)
453
+ max_id = scope.maximum(primary_key)
454
+ batches_count = ((max_id - starting_id + 1) / batch_size.to_f).ceil
455
+
456
+ batches_count.times do |i|
457
+ batch_id = i + 1
458
+ min_id = starting_id + (i * batch_size)
459
+ bulk_reindex_job scope, batch_id, min_id: min_id, max_id: min_id + batch_size - 1
460
+ end
461
+ else
462
+ scope.find_in_batches(batch_size: batch_size).each_with_index do |batch, i|
463
+ batch_id = i + 1
464
+
465
+ bulk_reindex_job scope, batch_id, record_ids: batch.map { |record| record.id.to_s }
466
+ end
431
467
  end
432
468
  else
433
469
  batch_id = 1
@@ -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: Searchkick.default_analyzed_analyzer}
237
+ analyzed_field_options = {type: default_type, index: index_true_value, analyzer: Searchkick.default_analyzed_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
@@ -16,7 +16,7 @@ module Searchkick
16
16
 
17
17
  def initialize(klass, term = "*", **options)
18
18
  unknown_keywords = options.keys - [:aggs, :body, :body_options, :boost,
19
- :boost_by, :boost_by_distance, :boost_where, :conversions, :debug, :emoji, :exclude, :execute, :explain,
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
21
  :match, :misspellings, :offset, :operator, :order, :padding, :page, :per_page, :profile,
22
22
  :request_params, :routing, :select, :similar, :smart_aggs, :suggest, :track, :type, :where]
@@ -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
@@ -159,6 +159,10 @@ module Searchkick
159
159
  @execute = Searchkick::Results.new(searchkick_klass, response, opts)
160
160
  end
161
161
 
162
+ def retry_misspellings?(response)
163
+ @misspellings_below && response["hits"]["total"] < @misspellings_below
164
+ end
165
+
162
166
  private
163
167
 
164
168
  def handle_error(e)
@@ -381,7 +385,7 @@ module Searchkick
381
385
  boost_mode: "replace",
382
386
  query: {
383
387
  match: {
384
- "#{conversions_field}.query" => term
388
+ "#{conversions_field}.query" => options[:conversions_term] || term
385
389
  }
386
390
  }
387
391
  }.merge(script_score)
@@ -447,7 +451,7 @@ module Searchkick
447
451
  set_aggregations(payload) if options[:aggs]
448
452
 
449
453
  # suggestions
450
- set_suggestions(payload) if options[:suggest]
454
+ set_suggestions(payload, options[:suggest]) if options[:suggest]
451
455
 
452
456
  # highlight
453
457
  set_highlights(payload, fields) if options[:highlight]
@@ -489,7 +493,8 @@ module Searchkick
489
493
 
490
494
  def set_fields
491
495
  boost_fields = {}
492
- fields = options[:fields] || searchkick_options[:searchable]
496
+ fields = options[:fields] || searchkick_options[:default_fields] || searchkick_options[:searchable]
497
+ all = searchkick_options.key?(:_all) ? searchkick_options[:_all] : below60?
493
498
  default_match = options[:match] || searchkick_options[:match] || :word
494
499
  fields =
495
500
  if fields
@@ -500,12 +505,12 @@ module Searchkick
500
505
  boost_fields[field] = boost.to_f if boost
501
506
  field
502
507
  end
503
- elsif default_match == :word
508
+ elsif all && default_match == :word
504
509
  ["_all"]
505
- elsif default_match == :phrase
510
+ elsif all && default_match == :phrase
506
511
  ["_all.phrase"]
507
512
  else
508
- raise ArgumentError, "Must specify fields"
513
+ raise ArgumentError, "Must specify fields to search"
509
514
  end
510
515
  [boost_fields, fields]
511
516
  end
@@ -575,12 +580,18 @@ module Searchkick
575
580
  payload[:indices_boost] = indices_boost
576
581
  end
577
582
 
578
- def set_suggestions(payload)
579
- suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
583
+ def set_suggestions(payload, suggest)
584
+ suggest_fields = nil
585
+
586
+ if suggest.is_a?(Array)
587
+ suggest_fields = suggest
588
+ else
589
+ suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
580
590
 
581
- # intersection
582
- if options[:fields]
583
- suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
591
+ # intersection
592
+ if options[:fields]
593
+ suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
594
+ end
584
595
  end
585
596
 
586
597
  if suggest_fields.any?
@@ -592,6 +603,8 @@ module Searchkick
592
603
  }
593
604
  }
594
605
  end
606
+ else
607
+ raise ArgumentError, "Must pass fields to suggest option"
595
608
  end
596
609
  end
597
610
 
@@ -822,7 +835,7 @@ module Searchkick
822
835
  if value.any?(&:nil?)
823
836
  {bool: {should: [term_filters(field, nil), term_filters(field, value.compact)]}}
824
837
  else
825
- {in: {field => value}}
838
+ {terms: {field => value}}
826
839
  end
827
840
  elsif value.nil?
828
841
  {bool: {must_not: {exists: {field: field}}}}
@@ -897,5 +910,9 @@ module Searchkick
897
910
  def below50?
898
911
  Searchkick.server_below?("5.0.0-alpha1")
899
912
  end
913
+
914
+ def below60?
915
+ Searchkick.server_below?("6.0.0-alpha1")
916
+ end
900
917
  end
901
918
  end
@@ -127,8 +127,14 @@ module Searchkick
127
127
  klass.model_name
128
128
  end
129
129
 
130
- def entry_name
131
- model_name.human.downcase
130
+ def entry_name(options = {})
131
+ if options.empty?
132
+ # backward compatibility
133
+ model_name.human.downcase
134
+ else
135
+ default = options[:count] == 1 ? model_name.human : model_name.human.pluralize
136
+ model_name.human(options.reverse_merge(default: default))
137
+ end
132
138
  end
133
139
 
134
140
  def total_count
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "2.3.3"
2
+ VERSION = "2.3.4"
3
3
  end
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
  :searchkick_search_analyzer, :searchkick_search2_analyzer, :default_analyzed_analyzer
41
42
  attr_writer :client, :env, :search_timeout
42
43
  attr_reader :aws_credentials
@@ -105,14 +106,8 @@ module Searchkick
105
106
  end
106
107
  end
107
108
 
108
- def self.multi_search(queries)
109
- if queries.any?
110
- responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
111
- queries.each_with_index do |query, i|
112
- query.handle_response(responses[i])
113
- end
114
- end
115
- queries
109
+ def self.multi_search(queries, retry_misspellings: false)
110
+ Searchkick::MultiSearch.new(queries, retry_misspellings: retry_misspellings).perform
116
111
  end
117
112
 
118
113
  # callbacks
@@ -157,6 +152,8 @@ module Searchkick
157
152
  completed: batches_left == 0,
158
153
  batches_left: batches_left
159
154
  }
155
+ else
156
+ raise Searchkick::Error, "Redis not configured"
160
157
  end
161
158
  end
162
159
 
data/test/boost_test.rb CHANGED
@@ -28,6 +28,18 @@ class BoostTest < Minitest::Test
28
28
  assert_order "speaker", ["Speaker A", "Speaker B", "Speaker C"], {conversions: "conversions_b"}, Speaker
29
29
  end
30
30
 
31
+ def test_multiple_conversions_with_boost_term
32
+ store [
33
+ {name: "Speaker A", conversions_a: {"speaker" => 4, "speaker_1" => 1}},
34
+ {name: "Speaker B", conversions_a: {"speaker" => 3, "speaker_1" => 2}},
35
+ {name: "Speaker C", conversions_a: {"speaker" => 2, "speaker_1" => 3}},
36
+ {name: "Speaker D", conversions_a: {"speaker" => 1, "speaker_1" => 4}}
37
+ ], Speaker
38
+
39
+ assert_order "speaker", ["Speaker A", "Speaker B", "Speaker C", "Speaker D"], {conversions: "conversions_a"}, Speaker
40
+ assert_order "speaker", ["Speaker D", "Speaker C", "Speaker B", "Speaker A"], {conversions: "conversions_a", conversions_term: "speaker_1"}, Speaker
41
+ end
42
+
31
43
  def test_conversions_stemmed
32
44
  store [
33
45
  {name: "Tomato A", conversions: {"tomato" => 1, "tomatos" => 1, "Tomatoes" => 1}},
@@ -6,3 +6,7 @@ gemspec path: "../../"
6
6
  gem "mongoid", "~> 6.0.0"
7
7
  gem "activejob"
8
8
  gem "redis"
9
+
10
+ # kaminari
11
+ gem "actionpack"
12
+ gem "kaminari"
@@ -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
@@ -132,13 +132,22 @@ class IndexTest < Minitest::Test
132
132
  assert_search "*", [], where: {alt_description: "Hello"}
133
133
  end
134
134
 
135
+ def test_filterable_non_string
136
+ store [{name: "Product A", store_id: 1}]
137
+ assert_search "*", ["Product A"], where: {store_id: 1}
138
+ end
139
+
135
140
  def test_large_value
136
141
  skip if nobrainer?
137
142
  large_value = 1000.times.map { "hello" }.join(" ")
138
143
  store [{name: "Product A", text: large_value}], Region
139
144
  assert_search "product", ["Product A"], {}, Region
140
145
  assert_search "hello", ["Product A"], {fields: [:name, :text]}, Region
141
- assert_search "hello", ["Product A"], {}, Region
146
+
147
+ # needs fields for ES 6
148
+ if elasticsearch_below60?
149
+ assert_search "hello", ["Product A"], {}, Region
150
+ end
142
151
  end
143
152
 
144
153
  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
@@ -50,4 +50,21 @@ class PaginationTest < Minitest::Test
50
50
  assert_equal 1, products.current_page
51
51
  assert products.first_page?
52
52
  end
53
+
54
+ def test_kaminari
55
+ skip unless defined?(Kaminari)
56
+
57
+ require "action_view"
58
+
59
+ I18n.load_path = Dir["test/support/kaminari.yml"]
60
+ I18n.backend.load_translations
61
+
62
+ view = ActionView::Base.new
63
+
64
+ store_names ["Product A"]
65
+ assert_equal "Displaying <b>1</b> product", view.page_entries_info(Product.search("product"))
66
+
67
+ store_names ["Product B"]
68
+ assert_equal "Displaying <b>all 2</b> products", view.page_entries_info(Product.search("product"))
69
+ end
53
70
  end
data/test/reindex_test.rb CHANGED
@@ -39,6 +39,18 @@ class ReindexTest < Minitest::Test
39
39
  assert_search "product", ["Product A"]
40
40
  end
41
41
 
42
+ def test_async_non_integer_pk
43
+ skip if !defined?(ActiveJob)
44
+
45
+ Sku.create(id: SecureRandom.hex, name: "Test")
46
+ reindex = Sku.reindex(async: true)
47
+ assert_search "sku", [], conversions: false
48
+
49
+ index = Searchkick::Index.new(reindex[:index_name])
50
+ index.refresh
51
+ assert_equal 1, index.total_docs
52
+ end
53
+
42
54
  def test_refresh_interval
43
55
  reindex = Product.reindex(refresh_interval: "30s", async: true, import: false)
44
56
  index = Searchkick::Index.new(reindex[:index_name])
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/suggest_test.rb CHANGED
@@ -67,6 +67,16 @@ class SuggestTest < Minitest::Test
67
67
  assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [{"name^2" => :word_start}]
68
68
  end
69
69
 
70
+ def test_multiple_models
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], fields: [:name]).suggestions.first
73
+ end
74
+
75
+ def test_multiple_models_no_fields
76
+ store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
77
+ assert_raises(ArgumentError) { Searchkick.search("How Big is a Tigre Shar", suggest: true) }
78
+ end
79
+
70
80
  protected
71
81
 
72
82
  def assert_suggest(term, expected, options = {})
@@ -0,0 +1,21 @@
1
+ en:
2
+ views:
3
+ pagination:
4
+ first: "&laquo; First"
5
+ last: "Last &raquo;"
6
+ previous: "&lsaquo; Prev"
7
+ next: "Next &rsaquo;"
8
+ truncate: "&hellip;"
9
+ helpers:
10
+ page_entries_info:
11
+ entry:
12
+ zero: "entries"
13
+ one: "entry"
14
+ other: "entries"
15
+ one_page:
16
+ display_entries:
17
+ zero: "No %{entry_name} found"
18
+ one: "Displaying <b>1</b> %{entry_name}"
19
+ other: "Displaying <b>all %{count}</b> %{entry_name}"
20
+ more_pages:
21
+ display_entries: "Displaying %{entry_name} <b>%{first}&nbsp;-&nbsp;%{last}</b> of <b>%{total}</b> in total"
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
@@ -111,6 +115,12 @@ if defined?(Mongoid)
111
115
 
112
116
  class Cat < Animal
113
117
  end
118
+
119
+ class Sku
120
+ include Mongoid::Document
121
+
122
+ field :name
123
+ end
114
124
  elsif defined?(NoBrainer)
115
125
  NoBrainer.configure do |config|
116
126
  config.app_name = :searchkick
@@ -171,6 +181,13 @@ elsif defined?(NoBrainer)
171
181
 
172
182
  class Cat < Animal
173
183
  end
184
+
185
+ class Sku
186
+ include NoBrainer::Document
187
+
188
+ field :id, type: String
189
+ field :name, type: String
190
+ end
174
191
  elsif defined?(Cequel)
175
192
  cequel =
176
193
  Cequel.connect(
@@ -252,6 +269,13 @@ elsif defined?(Cequel)
252
269
  class Cat < Animal
253
270
  end
254
271
 
272
+ class Sku
273
+ include Cequel::Record
274
+
275
+ key :id, :uuid
276
+ column :name, :text
277
+ end
278
+
255
279
  [Product, Store, Region, Speaker, Animal].each(&:synchronize_schema)
256
280
  else
257
281
  require "active_record"
@@ -339,6 +363,10 @@ else
339
363
  t.string :type
340
364
  end
341
365
 
366
+ ActiveRecord::Migration.create_table :skus, id: :uuid do |t|
367
+ t.string :name
368
+ end
369
+
342
370
  class Product < ActiveRecord::Base
343
371
  belongs_to :store
344
372
  end
@@ -361,6 +389,9 @@ else
361
389
 
362
390
  class Cat < Animal
363
391
  end
392
+
393
+ class Sku < ActiveRecord::Base
394
+ end
364
395
  end
365
396
 
366
397
  class Product
@@ -417,6 +448,7 @@ end
417
448
 
418
449
  class Store
419
450
  searchkick \
451
+ default_fields: elasticsearch_below60? ? nil : [:name],
420
452
  routing: true,
421
453
  merge_mappings: true,
422
454
  mappings: {
@@ -438,6 +470,7 @@ end
438
470
 
439
471
  class Region
440
472
  searchkick \
473
+ default_fields: elasticsearch_below60? ? nil : [:name],
441
474
  geo_shape: {
442
475
  territory: {tree: "quadtree", precision: "10km"}
443
476
  }
@@ -455,6 +488,7 @@ end
455
488
 
456
489
  class Speaker
457
490
  searchkick \
491
+ default_fields: elasticsearch_below60? ? nil : [:name],
458
492
  conversions: ["conversions_a", "conversions_b"]
459
493
 
460
494
  attr_accessor :conversions_a, :conversions_b, :aisle
@@ -470,6 +504,7 @@ end
470
504
 
471
505
  class Animal
472
506
  searchkick \
507
+ default_fields: elasticsearch_below60? ? nil : [:name],
473
508
  text_start: [:name],
474
509
  suggest: [:name],
475
510
  index_name: -> { "#{name.tableize}-#{Date.today.year}#{Searchkick.index_suffix}" },
@@ -477,6 +512,10 @@ class Animal
477
512
  # wordnet: true
478
513
  end
479
514
 
515
+ class Sku
516
+ searchkick callbacks: defined?(ActiveJob) ? :async : true
517
+ end
518
+
480
519
  Product.searchkick_index.delete if Product.searchkick_index.exists?
481
520
  Product.reindex
482
521
  Product.reindex # run twice for both index paths
@@ -493,6 +532,7 @@ class Minitest::Test
493
532
  Store.destroy_all
494
533
  Animal.destroy_all
495
534
  Speaker.destroy_all
535
+ Sku.destroy_all
496
536
  end
497
537
 
498
538
  protected
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchkick-hooopo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.3
4
+ version: 2.3.4
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-06-21 00:00:00.000000000 Z
11
+ date: 2017-09-04 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
@@ -170,6 +171,7 @@ files:
170
171
  - test/similar_test.rb
171
172
  - test/sql_test.rb
172
173
  - test/suggest_test.rb
174
+ - test/support/kaminari.yml
173
175
  - test/synonyms_test.rb
174
176
  - test/test_helper.rb
175
177
  - test/where_test.rb
@@ -193,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
195
  version: '0'
194
196
  requirements: []
195
197
  rubyforge_project:
196
- rubygems_version: 2.5.1
198
+ rubygems_version: 2.6.11
197
199
  signing_key:
198
200
  specification_version: 4
199
201
  summary: Searchkick learns what your users are looking for. As more people search,
@@ -245,6 +247,7 @@ test_files:
245
247
  - test/similar_test.rb
246
248
  - test/sql_test.rb
247
249
  - test/suggest_test.rb
250
+ - test/support/kaminari.yml
248
251
  - test/synonyms_test.rb
249
252
  - test/test_helper.rb
250
253
  - test/where_test.rb