searchkick 4.6.3 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|