searchkick 4.0.2 → 4.1.0

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
  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