searchkick 4.0.2 → 4.1.0

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
  SHA256:
3
- metadata.gz: cb0777c063adddb2c339761f92c7ffaf3646daf869b7360dafe662ce0b38056b
4
- data.tar.gz: '062070815d5b78a2cb3b7c4f0dc9408730e8facd72ce223d25a6bafe9bd46d12'
3
+ metadata.gz: 011efe75e8dd9c13d54a7278a8e3eb8e9d4e8ef3bca601842c4f022799c3d0f4
4
+ data.tar.gz: bc18d8b082717fe5ebbd8891c9eda584bd4f288af3898c19495ccdc9db9f04f7
5
5
  SHA512:
6
- metadata.gz: b3c08562e9a52dc950ff74ebb532beac936367e75c3a2e794079217caed899c566df50265e602c75d13e44eafd03c09821cfb10a88398d0897e5b0a85c76cf7d
7
- data.tar.gz: c211a672924cec7627d0bf9b1e700c8ef6d037a745a7867e39b3cf8302f8f84d163d6911a893b0f8adf4073bf3f624cb517224e96522d0b5e209b081300a12f7
6
+ metadata.gz: 03dc4717ee01f14c16725e98125865c1d3331fea5cb4086af979e4b06bdb6576e427ee06a1a478a5792b857e4c087c66e43db0d0799691bccc5782ce4a3100ca
7
+ data.tar.gz: b5ba0becde65ea65ca5e8ad7c4122119fbc9580ee445575ad90bae03ba868b09199eeae405a7203d91bda495289d37435dd9ec145672ec7e6141720e721123cf
@@ -1,3 +1,10 @@
1
+ ## 4.1.0
2
+
3
+ - Added `like` operator
4
+ - Added `exists` operator
5
+ - Added warnings for certain regular expressions
6
+ - Fixed anchored regular expressions
7
+
1
8
  ## 4.0.2
2
9
 
3
10
  - Added block form of `scroll`
data/README.md CHANGED
@@ -10,7 +10,7 @@ Searchkick handles:
10
10
  - special characters - `jalapeno` matches `jalapeño`
11
11
  - extra whitespace - `dishwasher` matches `dish washer`
12
12
  - misspellings - `zuchini` matches `zucchini`
13
- - custom synonyms - `qtip` matches `cotton swab`
13
+ - custom synonyms - `pop` matches `soda`
14
14
 
15
15
  Plus:
16
16
 
@@ -99,14 +99,19 @@ Where
99
99
 
100
100
  ```ruby
101
101
  where: {
102
- expires_at: {gt: Time.now}, # lt, gte, lte also available
103
- orders_count: 1..10, # equivalent to {gte: 1, lte: 10}
104
- aisle_id: [25, 30], # in
105
- store_id: {not: 2}, # not
106
- aisle_id: {not: [25, 30]}, # not in
107
- user_ids: {all: [1, 3]}, # all elements in array
108
- category: /frozen .+/, # regexp
109
- _or: [{in_stock: true}, {backordered: true}]
102
+ expires_at: {gt: Time.now}, # lt, gte, lte also available
103
+ orders_count: 1..10, # equivalent to {gte: 1, lte: 10}
104
+ aisle_id: [25, 30], # in
105
+ store_id: {not: 2}, # not
106
+ aisle_id: {not: [25, 30]}, # not in
107
+ user_ids: {all: [1, 3]}, # all elements in array
108
+ category: {like: "%frozen%"}, # like
109
+ category: /frozen .+/, # regexp
110
+ category: {prefix: "frozen"}, # prefix
111
+ store_id: {exists: true}, # exists
112
+ _or: [{in_stock: true}, {backordered: true}],
113
+ _and: [{in_stock: true}, {backordered: true}],
114
+ _not: {store_id: 1} # negate a condition
110
115
  }
111
116
  ```
112
117
 
@@ -313,7 +318,7 @@ A few languages require plugins:
313
318
 
314
319
  ```ruby
315
320
  class Product < ApplicationRecord
316
- searchkick synonyms: [["burger", "hamburger"], ["sneakers", "shoes"]]
321
+ searchkick synonyms: [["pop", "soda"], ["burger", "hamburger"]]
317
322
  end
318
323
  ```
319
324
 
@@ -631,7 +636,7 @@ Autocomplete predicts what a user will type, making the search experience faster
631
636
 
632
637
  ![Autocomplete](https://gist.github.com/ankane/b6988db2802aca68a589b31e41b44195/raw/40febe948427e5bc53ec4e5dc248822855fef76f/autocomplete.png)
633
638
 
634
- **Note:** To autocomplete on general categories (like `cereal` rather than product names), check out [Autosuggest](https://github.com/ankane/autosuggest).
639
+ **Note:** To autocomplete on search terms rather than results, check out [Autosuggest](https://github.com/ankane/autosuggest).
635
640
 
636
641
  **Note 2:** If you only have a few thousand records, don’t use Searchkick for autocomplete. It’s *much* faster to load all records into JavaScript and autocomplete there (eliminates network requests).
637
642
 
@@ -1161,7 +1166,7 @@ If you run into issues on Windows, check out [this post](https://www.rastating.c
1161
1166
 
1162
1167
  ### Searchable Fields
1163
1168
 
1164
- By default, all string fields are searchable (can be used in `fields` option). Speed up indexing and reduce index size by only making some fields searchable. This disables the `_all` field unless it’s listed.
1169
+ By default, all string fields are searchable (can be used in `fields` option). Speed up indexing and reduce index size by only making some fields searchable.
1165
1170
 
1166
1171
  ```ruby
1167
1172
  class Product < ApplicationRecord
@@ -49,7 +49,7 @@ module Searchkick
49
49
 
50
50
  def self.client
51
51
  @client ||= begin
52
- require "typhoeus/adapters/faraday" if defined?(Typhoeus)
52
+ require "typhoeus/adapters/faraday" if defined?(Typhoeus) && Gem::Version.new(Faraday::VERSION) < Gem::Version.new("0.14.0")
53
53
 
54
54
  Elasticsearch::Client.new({
55
55
  url: ENV["ELASTICSEARCH_URL"],
@@ -192,6 +192,10 @@ module Searchkick
192
192
  end
193
193
  end
194
194
 
195
+ def self.warn(message)
196
+ super("[searchkick] WARNING: #{message}")
197
+ end
198
+
195
199
  # private
196
200
  def self.load_records(records, ids)
197
201
  records =
@@ -61,7 +61,7 @@ module Searchkick
61
61
  if records.any?
62
62
  if async
63
63
  Searchkick::BulkReindexJob.perform_later(
64
- class_name: records.first.class.name,
64
+ class_name: records.first.class.searchkick_options[:class_name],
65
65
  record_ids: records.map(&:id),
66
66
  index_name: index.name,
67
67
  method_name: method_name ? method_name.to_s : nil
@@ -140,7 +140,7 @@ module Searchkick
140
140
  def bulk_reindex_job(scope, batch_id, options)
141
141
  Searchkick.with_redis { |r| r.sadd(batches_key, batch_id) }
142
142
  Searchkick::BulkReindexJob.perform_later({
143
- class_name: scope.model_name.name,
143
+ class_name: scope.searchkick_options[:class_name],
144
144
  index_name: index.name,
145
145
  batch_id: batch_id
146
146
  }.merge(options))
@@ -259,6 +259,10 @@ module Searchkick
259
259
  @bulk_indexer ||= BulkIndexer.new(self)
260
260
  end
261
261
 
262
+ def import_before_promotion(index, relation, **import_options)
263
+ index.import_scope(relation, **import_options)
264
+ end
265
+
262
266
  # https://gist.github.com/jarosan/3124884
263
267
  # http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/
264
268
  def reindex_scope(relation, import: true, resume: false, retain: false, async: false, refresh_interval: nil, scope: nil)
@@ -284,8 +288,7 @@ module Searchkick
284
288
  # check if alias exists
285
289
  alias_exists = alias_exists?
286
290
  if alias_exists
287
- # import before promotion
288
- index.import_scope(relation, **import_options) if import
291
+ import_before_promotion(index, relation, **import_options) if import
289
292
 
290
293
  # get existing indices to remove
291
294
  unless async
@@ -114,7 +114,7 @@ module Searchkick
114
114
  type: "shingle",
115
115
  token_separator: ""
116
116
  },
117
- # lucky find http://web.archiveorange.com/archive/v/AAfXfQ17f57FcRINsof7
117
+ # lucky find https://web.archiveorange.com/archive/v/AAfXfQ17f57FcRINsof7
118
118
  searchkick_search_shingle: {
119
119
  type: "shingle",
120
120
  token_separator: "",
@@ -15,6 +15,7 @@ module Searchkick
15
15
  Searchkick.models << self
16
16
 
17
17
  options[:_type] ||= -> { searchkick_index.klass_document_type(self, true) }
18
+ options[:class_name] = model_name.name
18
19
 
19
20
  callbacks = options.key?(:callbacks) ? options[:callbacks] : :inline
20
21
  unless [:inline, true, false, :async, :queue].include?(callbacks)
@@ -44,8 +45,8 @@ module Searchkick
44
45
  end
45
46
  alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
46
47
 
47
- def searchkick_index
48
- index = class_variable_get(:@@searchkick_index)
48
+ def searchkick_index(name: nil)
49
+ index = name || class_variable_get(:@@searchkick_index)
49
50
  index = index.call if index.respond_to?(:call)
50
51
  index_cache = class_variable_get(:@@searchkick_index_cache)
51
52
  index_cache[index] ||= Searchkick::Index.new(index, searchkick_options)
@@ -2,7 +2,7 @@ module Searchkick
2
2
  class ProcessBatchJob < ActiveJob::Base
3
3
  queue_as { Searchkick.queue_name }
4
4
 
5
- def perform(class_name:, record_ids:)
5
+ def perform(class_name:, record_ids:, index_name: nil)
6
6
  # separate routing from id
7
7
  routing = Hash[record_ids.map { |r| r.split(/(?<!\|)\|(?!\|)/, 2).map { |v| v.gsub("||", "|") } }]
8
8
  record_ids = routing.keys
@@ -26,7 +26,7 @@ module Searchkick
26
26
  end
27
27
 
28
28
  # bulk reindex
29
- index = klass.searchkick_index
29
+ index = klass.searchkick_index(name: index_name)
30
30
  Searchkick.callbacks(:bulk) do
31
31
  index.bulk_index(records) if records.any?
32
32
  index.bulk_delete(delete_records) if delete_records.any?
@@ -2,21 +2,29 @@ module Searchkick
2
2
  class ProcessQueueJob < ActiveJob::Base
3
3
  queue_as { Searchkick.queue_name }
4
4
 
5
- def perform(class_name:)
5
+ def perform(class_name:, index_name: nil, inline: false)
6
6
  model = class_name.constantize
7
+ limit = model.searchkick_options[:batch_size] || 1000
7
8
 
8
- limit = model.searchkick_index.options[:batch_size] || 1000
9
- record_ids = model.searchkick_index.reindex_queue.reserve(limit: limit)
10
- if record_ids.any?
11
- Searchkick::ProcessBatchJob.perform_later(
12
- class_name: model.name,
13
- record_ids: record_ids
14
- )
15
- # TODO when moving to reliable queuing, mark as complete
9
+ loop do
10
+ record_ids = model.searchkick_index(name: index_name).reindex_queue.reserve(limit: limit)
11
+ if record_ids.any?
12
+ batch_options = {
13
+ class_name: class_name,
14
+ record_ids: record_ids,
15
+ index_name: index_name
16
+ }
16
17
 
17
- if record_ids.size == limit
18
- Searchkick::ProcessQueueJob.perform_later(class_name: class_name)
18
+ if inline
19
+ # use new.perform to avoid excessive logging
20
+ Searchkick::ProcessBatchJob.new.perform(**batch_options)
21
+ else
22
+ Searchkick::ProcessBatchJob.perform_later(**batch_options)
23
+ end
24
+
25
+ # TODO when moving to reliable queuing, mark as complete
19
26
  end
27
+ break unless record_ids.size == limit
20
28
  end
21
29
  end
22
30
  end
@@ -137,6 +137,7 @@ module Searchkick
137
137
  }
138
138
 
139
139
  if options[:debug]
140
+ # can remove when minimum Ruby version is 2.5
140
141
  require "pp"
141
142
 
142
143
  puts "Searchkick Version: #{Searchkick::VERSION}"
@@ -432,7 +433,7 @@ module Searchkick
432
433
 
433
434
  models = Array(options[:models])
434
435
  if models.any? { |m| m != m.searchkick_klass }
435
- warn "[searchkick] WARNING: Passing child models to models option throws off hits and pagination - use type option instead"
436
+ Searchkick.warn("Passing child models to models option throws off hits and pagination - use type option instead")
436
437
 
437
438
  # uncomment once aliases are supported with _index
438
439
  # see https://github.com/elastic/elasticsearch/issues/23306
@@ -873,6 +874,8 @@ module Searchkick
873
874
  filters << {bool: {must_not: where_filters(value)}}
874
875
  elsif field == :_and
875
876
  filters << {bool: {must: value.map { |or_statement| {bool: {filter: where_filters(or_statement)}} }}}
877
+ # elsif field == :_script
878
+ # filters << {script: {script: {source: value, lang: "painless"}}}
876
879
  else
877
880
  # expand ranges
878
881
  if value.is_a?(Range)
@@ -933,6 +936,14 @@ module Searchkick
933
936
  }
934
937
  }
935
938
  }
939
+ when :like
940
+ # based on Postgres
941
+ # https://www.postgresql.org/docs/current/functions-matching.html
942
+ # % matches zero or more characters
943
+ # _ matches one character
944
+ # \ is escape character
945
+ regex = Regexp.escape(op_value).gsub(/(?<!\\)%/, ".*").gsub(/(?<!\\)_/, ".").gsub("\\%", "%").gsub("\\_", "_")
946
+ filters << {regexp: {field => {value: regex}}}
936
947
  when :prefix
937
948
  filters << {prefix: {field => op_value}}
938
949
  when :regexp # support for regexp queries without using a regexp ruby object
@@ -945,6 +956,8 @@ module Searchkick
945
956
  end
946
957
  when :in
947
958
  filters << term_filters(field, op_value)
959
+ when :exists
960
+ filters << {exists: {field: field}}
948
961
  else
949
962
  range_query =
950
963
  case op
@@ -985,7 +998,32 @@ module Searchkick
985
998
  elsif value.nil?
986
999
  {bool: {must_not: {exists: {field: field}}}}
987
1000
  elsif value.is_a?(Regexp)
988
- {regexp: {field => {value: value.source, flags: "NONE"}}}
1001
+ if value.casefold?
1002
+ Searchkick.warn("Case-insensitive flag does not work with Elasticsearch")
1003
+ end
1004
+
1005
+ source = value.source
1006
+ unless source.start_with?("\\A") && source.end_with?("\\z")
1007
+ # https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html
1008
+ Searchkick.warn("Regular expressions are always anchored in Elasticsearch")
1009
+ end
1010
+
1011
+ # TODO handle other anchor characters, like ^, $, \Z
1012
+ if source.start_with?("\\A")
1013
+ source = source[2..-1]
1014
+ else
1015
+ # TODO uncomment in Searchkick 5
1016
+ # source = ".*#{source}"
1017
+ end
1018
+
1019
+ if source.end_with?("\\z")
1020
+ source = source[0..-3]
1021
+ else
1022
+ # TODO uncomment in Searchkick 5
1023
+ # source = "#{source}.*"
1024
+ end
1025
+
1026
+ {regexp: {field => {value: source, flags: "NONE"}}}
989
1027
  else
990
1028
  {term: {field => value}}
991
1029
  end
@@ -62,7 +62,7 @@ module Searchkick
62
62
  end
63
63
 
64
64
  if missing_ids.any?
65
- warn "[searchkick] WARNING: Records in search index do not exist in database: #{missing_ids.join(", ")}"
65
+ Searchkick.warn("Records in search index do not exist in database: #{missing_ids.join(", ")}")
66
66
  end
67
67
 
68
68
  results
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "4.0.2"
2
+ VERSION = "4.1.0"
3
3
  end
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: 4.0.2
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-05 00:00:00.000000000 Z
11
+ date: 2019-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -145,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  requirements: []
148
- rubygems_version: 3.0.3
148
+ rubygems_version: 3.0.4
149
149
  signing_key:
150
150
  specification_version: 4
151
151
  summary: Intelligent search made easy with Rails and Elasticsearch