searchkick 4.6.3 → 5.0.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 +4 -4
- data/CHANGELOG.md +26 -1
- data/README.md +108 -55
- data/lib/searchkick/bulk_reindex_job.rb +12 -8
- data/lib/searchkick/controller_runtime.rb +40 -0
- data/lib/searchkick/index.rb +121 -54
- data/lib/searchkick/index_cache.rb +30 -0
- data/lib/searchkick/index_options.rb +16 -66
- data/lib/searchkick/indexer.rb +15 -8
- data/lib/searchkick/log_subscriber.rb +57 -0
- data/lib/searchkick/middleware.rb +1 -1
- data/lib/searchkick/model.rb +44 -47
- data/lib/searchkick/process_batch_job.rb +9 -25
- data/lib/searchkick/process_queue_job.rb +3 -2
- data/lib/searchkick/query.rb +36 -52
- data/lib/searchkick/record_data.rb +0 -1
- data/lib/searchkick/record_indexer.rb +135 -51
- data/lib/searchkick/reindex_queue.rb +25 -2
- data/lib/searchkick/reindex_v2_job.rb +10 -34
- data/lib/searchkick/relation.rb +36 -0
- data/lib/searchkick/relation_indexer.rb +150 -0
- data/lib/searchkick/results.rb +22 -23
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick.rb +157 -82
- data/lib/tasks/searchkick.rake +6 -3
- metadata +11 -28
- data/lib/searchkick/bulk_indexer.rb +0 -173
- data/lib/searchkick/logging.rb +0 -246
data/lib/searchkick/model.rb
CHANGED
@@ -7,7 +7,7 @@ module Searchkick
|
|
7
7
|
:filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language,
|
8
8
|
:locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity,
|
9
9
|
:special_characters, :stem, :stemmer, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end,
|
10
|
-
:text_middle, :text_start, :
|
10
|
+
:text_middle, :text_start, :unscope, :word, :word_end, :word_middle, :word_start]
|
11
11
|
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
12
12
|
|
13
13
|
raise "Only call searchkick once per model" if respond_to?(:searchkick_index)
|
@@ -22,52 +22,76 @@ module Searchkick
|
|
22
22
|
raise ArgumentError, "Invalid value for callbacks"
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
else
|
31
|
-
[options.key?(:index_prefix) ? options[:index_prefix] : Searchkick.index_prefix, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_")
|
25
|
+
mod = Module.new
|
26
|
+
include(mod)
|
27
|
+
mod.module_eval do
|
28
|
+
def reindex(method_name = nil, mode: nil, refresh: false)
|
29
|
+
self.class.searchkick_index.reindex([self], method_name: method_name, mode: mode, refresh: refresh, single: true)
|
32
30
|
end
|
33
31
|
|
32
|
+
def similar(**options)
|
33
|
+
self.class.searchkick_index.similar_record(self, **options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def search_data
|
37
|
+
data = respond_to?(:to_hash) ? to_hash : serializable_hash
|
38
|
+
data.delete("id")
|
39
|
+
data.delete("_id")
|
40
|
+
data.delete("_type")
|
41
|
+
data
|
42
|
+
end
|
43
|
+
|
44
|
+
def should_index?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
34
49
|
class_eval do
|
35
|
-
cattr_reader :searchkick_options, :searchkick_klass
|
50
|
+
cattr_reader :searchkick_options, :searchkick_klass, instance_reader: false
|
36
51
|
|
37
52
|
class_variable_set :@@searchkick_options, options.dup
|
38
53
|
class_variable_set :@@searchkick_klass, self
|
39
|
-
class_variable_set :@@
|
40
|
-
class_variable_set :@@searchkick_index_cache, {}
|
54
|
+
class_variable_set :@@searchkick_index_cache, Searchkick::IndexCache.new
|
41
55
|
|
42
56
|
class << self
|
43
57
|
def searchkick_search(term = "*", **options, &block)
|
44
|
-
|
45
|
-
|
58
|
+
if Searchkick.relation?(self)
|
59
|
+
raise Searchkick::Error, "search must be called on model, not relation"
|
60
|
+
end
|
46
61
|
|
47
62
|
Searchkick.search(term, model: self, **options, &block)
|
48
63
|
end
|
49
64
|
alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
|
50
65
|
|
51
66
|
def searchkick_index(name: nil)
|
52
|
-
index = name ||
|
67
|
+
index = name || searchkick_index_name
|
53
68
|
index = index.call if index.respond_to?(:call)
|
54
69
|
index_cache = class_variable_get(:@@searchkick_index_cache)
|
55
|
-
index_cache
|
70
|
+
index_cache.fetch(index) { Searchkick::Index.new(index, searchkick_options) }
|
56
71
|
end
|
57
72
|
alias_method :search_index, :searchkick_index unless method_defined?(:search_index)
|
58
73
|
|
59
74
|
def searchkick_reindex(method_name = nil, **options)
|
60
|
-
|
61
|
-
relation = (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
|
62
|
-
(respond_to?(:queryable) && queryable != unscoped.with_default_scope)
|
63
|
-
|
64
|
-
searchkick_index.reindex(searchkick_klass, method_name, scoped: relation, **options)
|
75
|
+
searchkick_index.reindex(self, method_name: method_name, **options)
|
65
76
|
end
|
66
77
|
alias_method :reindex, :searchkick_reindex unless method_defined?(:reindex)
|
67
78
|
|
68
79
|
def searchkick_index_options
|
69
80
|
searchkick_index.index_options
|
70
81
|
end
|
82
|
+
|
83
|
+
def searchkick_index_name
|
84
|
+
@searchkick_index_name ||= begin
|
85
|
+
options = class_variable_get(:@@searchkick_options)
|
86
|
+
if options[:index_name]
|
87
|
+
options[:index_name]
|
88
|
+
elsif options[:index_prefix].respond_to?(:call)
|
89
|
+
-> { [options[:index_prefix].call, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_") }
|
90
|
+
else
|
91
|
+
[options.key?(:index_prefix) ? options[:index_prefix] : Searchkick.index_prefix, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
71
95
|
end
|
72
96
|
|
73
97
|
# always add callbacks, even when callbacks is false
|
@@ -78,33 +102,6 @@ module Searchkick
|
|
78
102
|
after_save :reindex, if: -> { Searchkick.callbacks?(default: callbacks) }
|
79
103
|
after_destroy :reindex, if: -> { Searchkick.callbacks?(default: callbacks) }
|
80
104
|
end
|
81
|
-
|
82
|
-
def reindex(method_name = nil, **options)
|
83
|
-
RecordIndexer.new(self).reindex(method_name, **options)
|
84
|
-
end unless method_defined?(:reindex)
|
85
|
-
|
86
|
-
# TODO switch to keyword arguments
|
87
|
-
def similar(options = {})
|
88
|
-
self.class.searchkick_index.similar_record(self, **options)
|
89
|
-
end unless method_defined?(:similar)
|
90
|
-
|
91
|
-
def search_data
|
92
|
-
data = respond_to?(:to_hash) ? to_hash : serializable_hash
|
93
|
-
data.delete("id")
|
94
|
-
data.delete("_id")
|
95
|
-
data.delete("_type")
|
96
|
-
data
|
97
|
-
end unless method_defined?(:search_data)
|
98
|
-
|
99
|
-
def should_index?
|
100
|
-
true
|
101
|
-
end unless method_defined?(:should_index?)
|
102
|
-
|
103
|
-
if defined?(Cequel) && self < Cequel::Record && !method_defined?(:destroyed?)
|
104
|
-
def destroyed?
|
105
|
-
transient?
|
106
|
-
end
|
107
|
-
end
|
108
105
|
end
|
109
106
|
end
|
110
107
|
end
|
@@ -3,34 +3,18 @@ module Searchkick
|
|
3
3
|
queue_as { Searchkick.queue_name }
|
4
4
|
|
5
5
|
def perform(class_name:, record_ids:, index_name: nil)
|
6
|
-
|
7
|
-
|
8
|
-
record_ids = routing.keys
|
6
|
+
model = Searchkick.load_model(class_name)
|
7
|
+
index = model.searchkick_index(name: index_name)
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# determine which records to delete
|
16
|
-
delete_ids = record_ids - records.map { |r| r.id.to_s }
|
17
|
-
delete_records = delete_ids.map do |id|
|
18
|
-
m = klass.new
|
19
|
-
m.id = id
|
20
|
-
if routing[id]
|
21
|
-
m.define_singleton_method(:search_routing) do
|
22
|
-
routing[id]
|
23
|
-
end
|
9
|
+
items =
|
10
|
+
record_ids.map do |r|
|
11
|
+
parts = r.split(/(?<!\|)\|(?!\|)/, 2)
|
12
|
+
.map { |v| v.gsub("||", "|") }
|
13
|
+
{id: parts[0], routing: parts[1]}
|
24
14
|
end
|
25
|
-
m
|
26
|
-
end
|
27
15
|
|
28
|
-
|
29
|
-
index
|
30
|
-
Searchkick.callbacks(:bulk) do
|
31
|
-
index.bulk_index(records) if records.any?
|
32
|
-
index.bulk_delete(delete_records) if delete_records.any?
|
33
|
-
end
|
16
|
+
relation = Searchkick.scope(model)
|
17
|
+
RecordIndexer.new(index).reindex_items(relation, items, method_name: nil)
|
34
18
|
end
|
35
19
|
end
|
36
20
|
end
|
@@ -3,11 +3,12 @@ module Searchkick
|
|
3
3
|
queue_as { Searchkick.queue_name }
|
4
4
|
|
5
5
|
def perform(class_name:, index_name: nil, inline: false)
|
6
|
-
model = class_name
|
6
|
+
model = Searchkick.load_model(class_name)
|
7
|
+
index = model.searchkick_index(name: index_name)
|
7
8
|
limit = model.searchkick_options[:batch_size] || 1000
|
8
9
|
|
9
10
|
loop do
|
10
|
-
record_ids =
|
11
|
+
record_ids = index.reindex_queue.reserve(limit: limit)
|
11
12
|
if record_ids.any?
|
12
13
|
batch_options = {
|
13
14
|
class_name: class_name,
|
data/lib/searchkick/query.rb
CHANGED
@@ -18,7 +18,7 @@ module Searchkick
|
|
18
18
|
|
19
19
|
def initialize(klass, term = "*", **options)
|
20
20
|
unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost,
|
21
|
-
:boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :
|
21
|
+
:boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :explain,
|
22
22
|
:fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
|
23
23
|
:match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
|
24
24
|
:request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where]
|
@@ -148,9 +148,6 @@ module Searchkick
|
|
148
148
|
}
|
149
149
|
|
150
150
|
if options[:debug]
|
151
|
-
# can remove when minimum Ruby version is 2.5
|
152
|
-
require "pp"
|
153
|
-
|
154
151
|
puts "Searchkick Version: #{Searchkick::VERSION}"
|
155
152
|
puts "Elasticsearch Version: #{Searchkick.server_version}"
|
156
153
|
puts
|
@@ -210,14 +207,14 @@ module Searchkick
|
|
210
207
|
e.message.include?("No query registered for [function_score]")
|
211
208
|
)
|
212
209
|
|
213
|
-
raise UnsupportedVersionError
|
210
|
+
raise UnsupportedVersionError
|
214
211
|
elsif status_code == 400
|
215
212
|
if (
|
216
213
|
e.message.include?("bool query does not support [filter]") ||
|
217
214
|
e.message.include?("[bool] filter does not support [filter]")
|
218
215
|
)
|
219
216
|
|
220
|
-
raise UnsupportedVersionError
|
217
|
+
raise UnsupportedVersionError
|
221
218
|
elsif e.message =~ /analyzer \[searchkick_.+\] not found/
|
222
219
|
raise InvalidQueryError, "Bad mapping - run #{reindex_command}"
|
223
220
|
else
|
@@ -233,7 +230,14 @@ module Searchkick
|
|
233
230
|
end
|
234
231
|
|
235
232
|
def execute_search
|
236
|
-
|
233
|
+
name = searchkick_klass ? "#{searchkick_klass.name} Search" : "Search"
|
234
|
+
event = {
|
235
|
+
name: name,
|
236
|
+
query: params
|
237
|
+
}
|
238
|
+
ActiveSupport::Notifications.instrument("search.searchkick", event) do
|
239
|
+
Searchkick.client.search(params)
|
240
|
+
end
|
237
241
|
end
|
238
242
|
|
239
243
|
def prepare
|
@@ -268,9 +272,10 @@ module Searchkick
|
|
268
272
|
should = []
|
269
273
|
|
270
274
|
if options[:similar]
|
275
|
+
like = options[:similar] == true ? term : options[:similar]
|
271
276
|
query = {
|
272
277
|
more_like_this: {
|
273
|
-
like:
|
278
|
+
like: like,
|
274
279
|
min_doc_freq: 1,
|
275
280
|
min_term_freq: 1,
|
276
281
|
analyzer: "searchkick_search2"
|
@@ -383,11 +388,6 @@ module Searchkick
|
|
383
388
|
|
384
389
|
if field.start_with?("*.")
|
385
390
|
q2 = qs.map { |q| {multi_match: q.merge(fields: [field], type: match_type == :match_phrase ? "phrase" : "best_fields")} }
|
386
|
-
if below61?
|
387
|
-
q2.each do |q|
|
388
|
-
q[:multi_match].delete(:fuzzy_transpositions)
|
389
|
-
end
|
390
|
-
end
|
391
391
|
else
|
392
392
|
q2 = qs.map { |q| {match_type => {field => q}} }
|
393
393
|
end
|
@@ -439,7 +439,7 @@ module Searchkick
|
|
439
439
|
payload = {}
|
440
440
|
|
441
441
|
# type when inheritance
|
442
|
-
where = (options[:where] || {}).dup
|
442
|
+
where = ensure_permitted(options[:where] || {}).dup
|
443
443
|
if searchkick_options[:inheritance] && (options[:type] || (klass != searchkick_klass && searchkick_index))
|
444
444
|
where[:type] = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v, true) }
|
445
445
|
end
|
@@ -696,9 +696,9 @@ module Searchkick
|
|
696
696
|
def set_boost_by(multiply_filters, custom_filters)
|
697
697
|
boost_by = options[:boost_by] || {}
|
698
698
|
if boost_by.is_a?(Array)
|
699
|
-
boost_by =
|
699
|
+
boost_by = boost_by.to_h { |f| [f, {factor: 1}] }
|
700
700
|
elsif boost_by.is_a?(Hash)
|
701
|
-
multiply_by, boost_by = boost_by.partition { |_, v| v.delete(:boost_mode) == "multiply" }.map
|
701
|
+
multiply_by, boost_by = boost_by.partition { |_, v| v.delete(:boost_mode) == "multiply" }.map(&:to_h)
|
702
702
|
end
|
703
703
|
boost_by[options[:boost]] = {factor: 1} if options[:boost]
|
704
704
|
|
@@ -763,7 +763,7 @@ module Searchkick
|
|
763
763
|
|
764
764
|
def set_highlights(payload, fields)
|
765
765
|
payload[:highlight] = {
|
766
|
-
fields:
|
766
|
+
fields: fields.to_h { |f| [f, {}] },
|
767
767
|
fragment_size: 0
|
768
768
|
}
|
769
769
|
|
@@ -797,7 +797,7 @@ module Searchkick
|
|
797
797
|
aggs = options[:aggs]
|
798
798
|
payload[:aggs] = {}
|
799
799
|
|
800
|
-
aggs =
|
800
|
+
aggs = aggs.to_h { |f| [f, {}] } if aggs.is_a?(Array) # convert to more advanced syntax
|
801
801
|
aggs.each do |field, agg_options|
|
802
802
|
size = agg_options[:limit] ? agg_options[:limit] : 1_000
|
803
803
|
shared_agg_options = agg_options.except(:limit, :field, :ranges, :date_ranges, :where)
|
@@ -836,8 +836,9 @@ module Searchkick
|
|
836
836
|
end
|
837
837
|
|
838
838
|
where = {}
|
839
|
-
where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
|
840
|
-
|
839
|
+
where = ensure_permitted(options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
|
840
|
+
agg_where = ensure_permitted(agg_options[:where] || {})
|
841
|
+
agg_filters = where_filters(where.merge(agg_where))
|
841
842
|
|
842
843
|
# only do one level comparison for simplicity
|
843
844
|
filters.select! do |filter|
|
@@ -873,19 +874,16 @@ module Searchkick
|
|
873
874
|
end
|
874
875
|
|
875
876
|
def set_order(payload)
|
876
|
-
|
877
|
-
id_field = :_id
|
878
|
-
# TODO no longer map id to _id in Searchkick 5
|
879
|
-
# since sorting on _id is deprecated in Elasticsearch
|
880
|
-
payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? id_field : k, v] }]
|
877
|
+
payload[:sort] = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
881
878
|
end
|
882
879
|
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
880
|
+
# provides *very* basic protection from unfiltered parameters
|
881
|
+
# this is not meant to be comprehensive and may be expanded in the future
|
882
|
+
def ensure_permitted(obj)
|
883
|
+
obj.to_h
|
884
|
+
end
|
888
885
|
|
886
|
+
def where_filters(where)
|
889
887
|
filters = []
|
890
888
|
(where || {}).each do |field, value|
|
891
889
|
field = :_id if field.to_s == "id"
|
@@ -1007,7 +1005,7 @@ module Searchkick
|
|
1007
1005
|
when :lte
|
1008
1006
|
{to: op_value, include_upper: true}
|
1009
1007
|
else
|
1010
|
-
raise "Unknown where operator: #{op.inspect}"
|
1008
|
+
raise ArgumentError, "Unknown where operator: #{op.inspect}"
|
1011
1009
|
end
|
1012
1010
|
# issue 132
|
1013
1011
|
if (existing = filters.find { |f| f[:range] && f[:range][field] })
|
@@ -1036,29 +1034,25 @@ module Searchkick
|
|
1036
1034
|
{bool: {must_not: {exists: {field: field}}}}
|
1037
1035
|
elsif value.is_a?(Regexp)
|
1038
1036
|
source = value.source
|
1039
|
-
|
1040
|
-
|
1041
|
-
Searchkick.warn("Regular expressions are always anchored in Elasticsearch")
|
1042
|
-
end
|
1037
|
+
|
1038
|
+
# TODO handle other regexp options
|
1043
1039
|
|
1044
1040
|
# TODO handle other anchor characters, like ^, $, \Z
|
1045
1041
|
if source.start_with?("\\A")
|
1046
1042
|
source = source[2..-1]
|
1047
1043
|
else
|
1048
|
-
|
1049
|
-
# source = ".*#{source}"
|
1044
|
+
source = ".*#{source}"
|
1050
1045
|
end
|
1051
1046
|
|
1052
1047
|
if source.end_with?("\\z")
|
1053
1048
|
source = source[0..-3]
|
1054
1049
|
else
|
1055
|
-
|
1056
|
-
# source = "#{source}.*"
|
1050
|
+
source = "#{source}.*"
|
1057
1051
|
end
|
1058
1052
|
|
1059
1053
|
if below710?
|
1060
1054
|
if value.casefold?
|
1061
|
-
|
1055
|
+
raise ArgumentError, "Case-insensitive flag does not work with Elasticsearch < 7.10"
|
1062
1056
|
end
|
1063
1057
|
{regexp: {field => {value: source, flags: "NONE"}}}
|
1064
1058
|
else
|
@@ -1069,9 +1063,7 @@ module Searchkick
|
|
1069
1063
|
if value.as_json.is_a?(Enumerable)
|
1070
1064
|
# query will fail, but this is better
|
1071
1065
|
# same message as Active Record
|
1072
|
-
|
1073
|
-
# raise InvalidQueryError for backward compatibility
|
1074
|
-
raise Searchkick::InvalidQueryError, "can't cast #{value.class.name}"
|
1066
|
+
raise TypeError, "can't cast #{value.class.name}"
|
1075
1067
|
end
|
1076
1068
|
|
1077
1069
|
{term: {field => {value: value}}}
|
@@ -1150,21 +1142,13 @@ module Searchkick
|
|
1150
1142
|
end
|
1151
1143
|
|
1152
1144
|
def track_total_hits?
|
1153
|
-
|
1145
|
+
searchkick_options[:deep_paging] || body_options[:track_total_hits]
|
1154
1146
|
end
|
1155
1147
|
|
1156
1148
|
def body_options
|
1157
1149
|
options[:body_options] || {}
|
1158
1150
|
end
|
1159
1151
|
|
1160
|
-
def below61?
|
1161
|
-
Searchkick.server_below?("6.1.0")
|
1162
|
-
end
|
1163
|
-
|
1164
|
-
def below70?
|
1165
|
-
Searchkick.server_below?("7.0.0")
|
1166
|
-
end
|
1167
|
-
|
1168
1152
|
def below73?
|
1169
1153
|
Searchkick.server_below?("7.3.0")
|
1170
1154
|
end
|
@@ -1,79 +1,163 @@
|
|
1
1
|
module Searchkick
|
2
2
|
class RecordIndexer
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :index
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@index = record.class.searchkick_index
|
5
|
+
def initialize(index)
|
6
|
+
@index = index
|
8
7
|
end
|
9
8
|
|
10
|
-
def reindex(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
mode ||= Searchkick.callbacks_value || index.options[:callbacks] || true
|
9
|
+
def reindex(records, mode:, method_name:, full: false, single: false)
|
10
|
+
# prevents exists? check if records is a relation
|
11
|
+
records = records.to_a
|
12
|
+
return if records.empty?
|
16
13
|
|
17
14
|
case mode
|
15
|
+
when :async
|
16
|
+
unless defined?(ActiveJob)
|
17
|
+
raise Searchkick::Error, "Active Job not found"
|
18
|
+
end
|
19
|
+
|
20
|
+
# we could likely combine ReindexV2Job, BulkReindexJob, and ProcessBatchJob
|
21
|
+
# but keep them separate for now
|
22
|
+
if single
|
23
|
+
record = records.first
|
24
|
+
|
25
|
+
# always pass routing in case record is deleted
|
26
|
+
# before the async job runs
|
27
|
+
if record.respond_to?(:search_routing)
|
28
|
+
routing = record.search_routing
|
29
|
+
end
|
30
|
+
|
31
|
+
Searchkick::ReindexV2Job.perform_later(
|
32
|
+
record.class.name,
|
33
|
+
record.id.to_s,
|
34
|
+
method_name ? method_name.to_s : nil,
|
35
|
+
routing: routing,
|
36
|
+
index_name: index.name
|
37
|
+
)
|
38
|
+
else
|
39
|
+
Searchkick::BulkReindexJob.perform_later(
|
40
|
+
class_name: records.first.class.searchkick_options[:class_name],
|
41
|
+
record_ids: records.map { |r| r.id.to_s },
|
42
|
+
index_name: index.name,
|
43
|
+
method_name: method_name ? method_name.to_s : nil
|
44
|
+
)
|
45
|
+
end
|
18
46
|
when :queue
|
19
47
|
if method_name
|
20
48
|
raise Searchkick::Error, "Partial reindex not supported with queue option"
|
21
49
|
end
|
22
50
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
51
|
+
index.reindex_queue.push_records(records)
|
52
|
+
when true, :inline
|
53
|
+
index_records, other_records = records.partition { |r| index_record?(r) }
|
54
|
+
import_inline(index_records, !full ? other_records : [], method_name: method_name, single: single)
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Invalid value for mode"
|
57
|
+
end
|
28
58
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
index.reindex_queue.push(value)
|
33
|
-
when :async
|
34
|
-
unless defined?(ActiveJob)
|
35
|
-
raise Searchkick::Error, "Active Job not found"
|
36
|
-
end
|
59
|
+
# return true like model and relation reindex for now
|
60
|
+
true
|
61
|
+
end
|
37
62
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
routing = record.search_routing
|
42
|
-
end
|
63
|
+
def reindex_items(klass, items, method_name:, single: false)
|
64
|
+
routing = items.to_h { |r| [r[:id], r[:routing]] }
|
65
|
+
record_ids = routing.keys
|
43
66
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
routing: routing
|
49
|
-
)
|
50
|
-
else # bulk, inline/true/nil
|
51
|
-
reindex_record(method_name)
|
67
|
+
relation = Searchkick.load_records(klass, record_ids)
|
68
|
+
# call search_import even for single records for nested associations
|
69
|
+
relation = relation.search_import if relation.respond_to?(:search_import)
|
70
|
+
records = relation.select(&:should_index?)
|
52
71
|
|
53
|
-
|
54
|
-
|
72
|
+
# determine which records to delete
|
73
|
+
delete_ids = record_ids - records.map { |r| r.id.to_s }
|
74
|
+
delete_records =
|
75
|
+
delete_ids.map do |id|
|
76
|
+
construct_record(klass, id, routing[id])
|
77
|
+
end
|
78
|
+
|
79
|
+
import_inline(records, delete_records, method_name: method_name, single: single)
|
55
80
|
end
|
56
81
|
|
57
82
|
private
|
58
83
|
|
59
|
-
def
|
60
|
-
|
84
|
+
def index_record?(record)
|
85
|
+
record.persisted? && !record.destroyed? && record.should_index?
|
61
86
|
end
|
62
87
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
88
|
+
# import in single request with retries
|
89
|
+
def import_inline(index_records, delete_records, method_name:, single:)
|
90
|
+
return if index_records.empty? && delete_records.empty?
|
91
|
+
|
92
|
+
maybe_bulk(index_records, delete_records, method_name, single) do
|
93
|
+
if index_records.any?
|
94
|
+
if method_name
|
95
|
+
index.bulk_update(index_records, method_name)
|
96
|
+
else
|
97
|
+
index.bulk_index(index_records)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if delete_records.any?
|
102
|
+
index.bulk_delete(delete_records)
|
70
103
|
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def maybe_bulk(index_records, delete_records, method_name, single)
|
108
|
+
if Searchkick.callbacks_value == :bulk
|
109
|
+
yield
|
71
110
|
else
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
111
|
+
# set action and data
|
112
|
+
action =
|
113
|
+
if single && index_records.empty?
|
114
|
+
"Remove"
|
115
|
+
elsif method_name
|
116
|
+
"Update"
|
117
|
+
else
|
118
|
+
single ? "Store" : "Import"
|
119
|
+
end
|
120
|
+
record = index_records.first || delete_records.first
|
121
|
+
name = record.class.searchkick_klass.name
|
122
|
+
message = lambda do |event|
|
123
|
+
event[:name] = "#{name} #{action}"
|
124
|
+
if single
|
125
|
+
event[:id] = index.search_id(record)
|
126
|
+
else
|
127
|
+
event[:count] = index_records.size + delete_records.size
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
with_retries do
|
132
|
+
Searchkick.callbacks(:bulk, message: message) do
|
133
|
+
yield
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def construct_record(klass, id, routing)
|
140
|
+
record = klass.new
|
141
|
+
record.id = id
|
142
|
+
if routing
|
143
|
+
record.define_singleton_method(:search_routing) do
|
144
|
+
routing
|
145
|
+
end
|
146
|
+
end
|
147
|
+
record
|
148
|
+
end
|
149
|
+
|
150
|
+
def with_retries
|
151
|
+
retries = 0
|
152
|
+
|
153
|
+
begin
|
154
|
+
yield
|
155
|
+
rescue Faraday::ClientError => e
|
156
|
+
if retries < 1
|
157
|
+
retries += 1
|
158
|
+
retry
|
76
159
|
end
|
160
|
+
raise e
|
77
161
|
end
|
78
162
|
end
|
79
163
|
end
|