searchkick 2.3.2 → 4.4.1
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 +5 -5
- data/CHANGELOG.md +251 -84
- data/LICENSE.txt +1 -1
- data/README.md +552 -432
- data/lib/searchkick/bulk_indexer.rb +173 -0
- data/lib/searchkick/bulk_reindex_job.rb +2 -2
- data/lib/searchkick/hash_wrapper.rb +12 -0
- data/lib/searchkick/index.rb +187 -348
- data/lib/searchkick/index_options.rb +494 -282
- data/lib/searchkick/logging.rb +17 -13
- data/lib/searchkick/model.rb +52 -97
- data/lib/searchkick/multi_search.rb +9 -10
- data/lib/searchkick/process_batch_job.rb +17 -4
- data/lib/searchkick/process_queue_job.rb +20 -12
- data/lib/searchkick/query.rb +415 -199
- data/lib/searchkick/railtie.rb +7 -0
- data/lib/searchkick/record_data.rb +128 -0
- data/lib/searchkick/record_indexer.rb +79 -0
- data/lib/searchkick/reindex_queue.rb +1 -1
- data/lib/searchkick/reindex_v2_job.rb +14 -12
- data/lib/searchkick/results.rb +135 -41
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick.rb +130 -61
- data/lib/tasks/searchkick.rake +34 -0
- metadata +18 -162
- data/.gitignore +0 -22
- data/.travis.yml +0 -39
- data/Gemfile +0 -16
- data/Rakefile +0 -20
- data/benchmark/Gemfile +0 -23
- data/benchmark/benchmark.rb +0 -97
- data/lib/searchkick/tasks.rb +0 -33
- data/searchkick.gemspec +0 -28
- data/test/aggs_test.rb +0 -197
- data/test/autocomplete_test.rb +0 -75
- data/test/boost_test.rb +0 -202
- data/test/callbacks_test.rb +0 -59
- data/test/ci/before_install.sh +0 -17
- data/test/errors_test.rb +0 -19
- data/test/gemfiles/activerecord31.gemfile +0 -7
- data/test/gemfiles/activerecord32.gemfile +0 -7
- data/test/gemfiles/activerecord40.gemfile +0 -8
- data/test/gemfiles/activerecord41.gemfile +0 -8
- data/test/gemfiles/activerecord42.gemfile +0 -7
- data/test/gemfiles/activerecord50.gemfile +0 -7
- data/test/gemfiles/apartment.gemfile +0 -8
- data/test/gemfiles/cequel.gemfile +0 -8
- data/test/gemfiles/mongoid2.gemfile +0 -7
- data/test/gemfiles/mongoid3.gemfile +0 -6
- data/test/gemfiles/mongoid4.gemfile +0 -7
- data/test/gemfiles/mongoid5.gemfile +0 -7
- data/test/gemfiles/mongoid6.gemfile +0 -12
- data/test/gemfiles/nobrainer.gemfile +0 -8
- data/test/gemfiles/parallel_tests.gemfile +0 -8
- data/test/geo_shape_test.rb +0 -175
- data/test/highlight_test.rb +0 -78
- data/test/index_test.rb +0 -166
- data/test/inheritance_test.rb +0 -83
- data/test/marshal_test.rb +0 -8
- data/test/match_test.rb +0 -276
- data/test/misspellings_test.rb +0 -56
- data/test/model_test.rb +0 -42
- data/test/multi_search_test.rb +0 -36
- data/test/multi_tenancy_test.rb +0 -22
- data/test/order_test.rb +0 -46
- data/test/pagination_test.rb +0 -70
- data/test/partial_reindex_test.rb +0 -58
- data/test/query_test.rb +0 -35
- data/test/records_test.rb +0 -10
- data/test/reindex_test.rb +0 -64
- data/test/reindex_v2_job_test.rb +0 -32
- data/test/routing_test.rb +0 -23
- data/test/should_index_test.rb +0 -32
- data/test/similar_test.rb +0 -28
- data/test/sql_test.rb +0 -214
- data/test/suggest_test.rb +0 -95
- data/test/support/kaminari.yml +0 -21
- data/test/synonyms_test.rb +0 -67
- data/test/test_helper.rb +0 -567
- data/test/where_test.rb +0 -223
data/lib/searchkick/logging.rb
CHANGED
@@ -129,10 +129,10 @@ module Searchkick
|
|
129
129
|
end
|
130
130
|
|
131
131
|
module SearchkickWithInstrumentation
|
132
|
-
def multi_search(searches
|
132
|
+
def multi_search(searches)
|
133
133
|
event = {
|
134
134
|
name: "Multi Search",
|
135
|
-
body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
|
135
|
+
body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join,
|
136
136
|
}
|
137
137
|
ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
|
138
138
|
super
|
@@ -162,12 +162,17 @@ module Searchkick
|
|
162
162
|
|
163
163
|
payload = event.payload
|
164
164
|
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
165
|
-
|
165
|
+
|
166
166
|
index = payload[:query][:index].is_a?(Array) ? payload[:query][:index].join(",") : payload[:query][:index]
|
167
|
+
type = payload[:query][:type]
|
168
|
+
request_params = payload[:query].except(:index, :type, :body)
|
167
169
|
|
168
|
-
|
169
|
-
|
170
|
-
|
170
|
+
params = []
|
171
|
+
request_params.each do |k, v|
|
172
|
+
params << "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
|
173
|
+
end
|
174
|
+
|
175
|
+
debug " #{color(name, YELLOW, true)} #{index}#{type ? "/#{type.join(',')}" : ''}/_search#{params.any? ? '?' + params.join('&') : nil} #{payload[:query][:body].to_json}"
|
171
176
|
end
|
172
177
|
|
173
178
|
def request(event)
|
@@ -187,9 +192,7 @@ module Searchkick
|
|
187
192
|
payload = event.payload
|
188
193
|
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
189
194
|
|
190
|
-
#
|
191
|
-
host = Searchkick.client.transport.hosts.first
|
192
|
-
debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/_msearch?pretty -d '#{payload[:body]}'"
|
195
|
+
debug " #{color(name, YELLOW, true)} _msearch #{payload[:body]}"
|
193
196
|
end
|
194
197
|
end
|
195
198
|
|
@@ -232,10 +235,11 @@ module Searchkick
|
|
232
235
|
end
|
233
236
|
end
|
234
237
|
end
|
235
|
-
|
236
|
-
Searchkick::
|
237
|
-
Searchkick::
|
238
|
-
Searchkick.
|
238
|
+
|
239
|
+
Searchkick::Query.prepend(Searchkick::QueryWithInstrumentation)
|
240
|
+
Searchkick::Index.prepend(Searchkick::IndexWithInstrumentation)
|
241
|
+
Searchkick::Indexer.prepend(Searchkick::IndexerWithInstrumentation)
|
242
|
+
Searchkick.singleton_class.prepend(Searchkick::SearchkickWithInstrumentation)
|
239
243
|
Searchkick::LogSubscriber.attach_to :searchkick
|
240
244
|
ActiveSupport.on_load(:action_controller) do
|
241
245
|
include Searchkick::ControllerRuntime
|
data/lib/searchkick/model.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
module Searchkick
|
2
2
|
module Model
|
3
3
|
def searchkick(**options)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
:
|
4
|
+
options = Searchkick.model_options.merge(options)
|
5
|
+
|
6
|
+
unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :deep_paging, :default_fields,
|
7
|
+
:filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language,
|
8
|
+
:locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity,
|
9
|
+
:special_characters, :stem, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end,
|
8
10
|
:text_middle, :text_start, :word, :wordnet, :word_end, :word_middle, :word_start]
|
9
11
|
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
10
12
|
|
@@ -12,63 +14,54 @@ module Searchkick
|
|
12
14
|
|
13
15
|
Searchkick.models << self
|
14
16
|
|
17
|
+
options[:_type] ||= -> { searchkick_index.klass_document_type(self, true) }
|
18
|
+
options[:class_name] = model_name.name
|
19
|
+
|
20
|
+
callbacks = options.key?(:callbacks) ? options[:callbacks] : :inline
|
21
|
+
unless [:inline, true, false, :async, :queue].include?(callbacks)
|
22
|
+
raise ArgumentError, "Invalid value for callbacks"
|
23
|
+
end
|
24
|
+
|
25
|
+
index_name =
|
26
|
+
if options[:index_name]
|
27
|
+
options[:index_name]
|
28
|
+
elsif options[:index_prefix].respond_to?(:call)
|
29
|
+
-> { [options[:index_prefix].call, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_") }
|
30
|
+
else
|
31
|
+
[options.key?(:index_prefix) ? options[:index_prefix] : Searchkick.index_prefix, model_name.plural, Searchkick.env, Searchkick.index_suffix].compact.join("_")
|
32
|
+
end
|
33
|
+
|
15
34
|
class_eval do
|
16
35
|
cattr_reader :searchkick_options, :searchkick_klass
|
17
36
|
|
18
|
-
callbacks = options.key?(:callbacks) ? options[:callbacks] : true
|
19
|
-
|
20
37
|
class_variable_set :@@searchkick_options, options.dup
|
21
38
|
class_variable_set :@@searchkick_klass, self
|
22
|
-
class_variable_set :@@
|
23
|
-
class_variable_set :@@
|
24
|
-
(options[:index_prefix].respond_to?(:call) && proc { [options[:index_prefix].call, 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("_")
|
39
|
+
class_variable_set :@@searchkick_index, index_name
|
40
|
+
class_variable_set :@@searchkick_index_cache, {}
|
26
41
|
|
27
42
|
class << self
|
28
43
|
def searchkick_search(term = "*", **options, &block)
|
29
|
-
|
44
|
+
# TODO throw error in next major version
|
45
|
+
Searchkick.warn("calling search on a relation is deprecated") if Searchkick.relation?(self)
|
46
|
+
|
47
|
+
Searchkick.search(term, model: self, **options, &block)
|
30
48
|
end
|
31
49
|
alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
|
32
50
|
|
33
|
-
def searchkick_index
|
34
|
-
index = class_variable_get
|
35
|
-
index = index.call if index.respond_to?
|
36
|
-
|
51
|
+
def searchkick_index(name: nil)
|
52
|
+
index = name || class_variable_get(:@@searchkick_index)
|
53
|
+
index = index.call if index.respond_to?(:call)
|
54
|
+
index_cache = class_variable_get(:@@searchkick_index_cache)
|
55
|
+
index_cache[index] ||= Searchkick::Index.new(index, searchkick_options)
|
37
56
|
end
|
38
57
|
alias_method :search_index, :searchkick_index unless method_defined?(:search_index)
|
39
58
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
def disable_search_callbacks
|
45
|
-
class_variable_set :@@searchkick_callbacks, false
|
46
|
-
end
|
47
|
-
|
48
|
-
def search_callbacks?
|
49
|
-
class_variable_get(:@@searchkick_callbacks) && Searchkick.callbacks?
|
50
|
-
end
|
51
|
-
|
52
|
-
def searchkick_reindex(method_name = nil, full: false, **options)
|
53
|
-
scoped = (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
|
59
|
+
def searchkick_reindex(method_name = nil, **options)
|
60
|
+
# TODO relation = Searchkick.relation?(self)
|
61
|
+
relation = (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
|
54
62
|
(respond_to?(:queryable) && queryable != unscoped.with_default_scope)
|
55
63
|
|
56
|
-
|
57
|
-
|
58
|
-
if method_name
|
59
|
-
# update
|
60
|
-
searchkick_index.import_scope(searchkick_klass, method_name: method_name)
|
61
|
-
searchkick_index.refresh if refresh
|
62
|
-
true
|
63
|
-
elsif scoped && !full
|
64
|
-
# reindex association
|
65
|
-
searchkick_index.import_scope(searchkick_klass)
|
66
|
-
searchkick_index.refresh if refresh
|
67
|
-
true
|
68
|
-
else
|
69
|
-
# full reindex
|
70
|
-
searchkick_index.reindex_scope(searchkick_klass, options)
|
71
|
-
end
|
64
|
+
searchkick_index.reindex(searchkick_klass, method_name, scoped: relation, **options)
|
72
65
|
end
|
73
66
|
alias_method :reindex, :searchkick_reindex unless method_defined?(:reindex)
|
74
67
|
|
@@ -77,68 +70,30 @@ module Searchkick
|
|
77
70
|
end
|
78
71
|
end
|
79
72
|
|
80
|
-
|
73
|
+
# always add callbacks, even when callbacks is false
|
74
|
+
# so Model.callbacks block can be used
|
81
75
|
if respond_to?(:after_commit)
|
82
|
-
after_commit
|
76
|
+
after_commit :reindex, if: -> { Searchkick.callbacks?(default: callbacks) }
|
83
77
|
elsif respond_to?(:after_save)
|
84
|
-
after_save
|
85
|
-
after_destroy
|
78
|
+
after_save :reindex, if: -> { Searchkick.callbacks?(default: callbacks) }
|
79
|
+
after_destroy :reindex, if: -> { Searchkick.callbacks?(default: callbacks) }
|
86
80
|
end
|
87
81
|
|
88
|
-
def reindex(method_name = nil,
|
89
|
-
|
90
|
-
|
91
|
-
if mode.nil?
|
92
|
-
mode =
|
93
|
-
if async
|
94
|
-
:async
|
95
|
-
elsif Searchkick.callbacks_value
|
96
|
-
Searchkick.callbacks_value
|
97
|
-
elsif klass_options.key?(:callbacks) && klass_options[:callbacks] != :async
|
98
|
-
# TODO remove 2nd condition in next major version
|
99
|
-
klass_options[:callbacks]
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
case mode
|
104
|
-
when :queue
|
105
|
-
if method_name
|
106
|
-
raise Searchkick::Error, "Partial reindex not supported with queue option"
|
107
|
-
else
|
108
|
-
self.class.searchkick_index.reindex_queue.push(id.to_s)
|
109
|
-
end
|
110
|
-
when :async
|
111
|
-
if method_name
|
112
|
-
# TODO support Mongoid and NoBrainer and non-id primary keys
|
113
|
-
Searchkick::BulkReindexJob.perform_later(
|
114
|
-
class_name: self.class.name,
|
115
|
-
record_ids: [id.to_s],
|
116
|
-
method_name: method_name ? method_name.to_s : nil
|
117
|
-
)
|
118
|
-
else
|
119
|
-
self.class.searchkick_index.reindex_record_async(self)
|
120
|
-
end
|
121
|
-
else
|
122
|
-
if method_name
|
123
|
-
self.class.searchkick_index.update_record(self, method_name)
|
124
|
-
else
|
125
|
-
self.class.searchkick_index.reindex_record(self)
|
126
|
-
end
|
127
|
-
self.class.searchkick_index.refresh if refresh
|
128
|
-
end
|
82
|
+
def reindex(method_name = nil, **options)
|
83
|
+
RecordIndexer.new(self).reindex(method_name, **options)
|
129
84
|
end unless method_defined?(:reindex)
|
130
85
|
|
131
|
-
# TODO
|
132
|
-
def reindex_async
|
133
|
-
reindex(async: true)
|
134
|
-
end unless method_defined?(:reindex_async)
|
135
|
-
|
86
|
+
# TODO switch to keyword arguments
|
136
87
|
def similar(options = {})
|
137
|
-
self.class.searchkick_index.similar_record(self, options)
|
88
|
+
self.class.searchkick_index.similar_record(self, **options)
|
138
89
|
end unless method_defined?(:similar)
|
139
90
|
|
140
91
|
def search_data
|
141
|
-
respond_to?(:to_hash) ? to_hash : serializable_hash
|
92
|
+
data = respond_to?(:to_hash) ? to_hash : serializable_hash
|
93
|
+
data.delete("id")
|
94
|
+
data.delete("_id")
|
95
|
+
data.delete("_type")
|
96
|
+
data
|
142
97
|
end unless method_defined?(:search_data)
|
143
98
|
|
144
99
|
def should_index?
|
@@ -2,25 +2,24 @@ module Searchkick
|
|
2
2
|
class MultiSearch
|
3
3
|
attr_reader :queries
|
4
4
|
|
5
|
-
def initialize(queries
|
5
|
+
def initialize(queries)
|
6
6
|
@queries = queries
|
7
|
-
@retry_misspellings = retry_misspellings
|
8
7
|
end
|
9
8
|
|
10
9
|
def perform
|
11
10
|
if queries.any?
|
12
|
-
perform_search(queries
|
11
|
+
perform_search(queries)
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
16
15
|
private
|
17
16
|
|
18
|
-
def perform_search(
|
19
|
-
responses = client.msearch(body:
|
17
|
+
def perform_search(search_queries, perform_retry: true)
|
18
|
+
responses = client.msearch(body: search_queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
|
20
19
|
|
21
20
|
retry_queries = []
|
22
|
-
|
23
|
-
if
|
21
|
+
search_queries.each_with_index do |query, i|
|
22
|
+
if perform_retry && query.retry_misspellings?(responses[i])
|
24
23
|
query.send(:prepare) # okay, since we don't want to expose this method outside Searchkick
|
25
24
|
retry_queries << query
|
26
25
|
else
|
@@ -28,11 +27,11 @@ module Searchkick
|
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
|
-
if
|
32
|
-
perform_search(retry_queries,
|
30
|
+
if retry_queries.any?
|
31
|
+
perform_search(retry_queries, perform_retry: false)
|
33
32
|
end
|
34
33
|
|
35
|
-
|
34
|
+
search_queries
|
36
35
|
end
|
37
36
|
|
38
37
|
def client
|
@@ -1,8 +1,12 @@
|
|
1
1
|
module Searchkick
|
2
2
|
class ProcessBatchJob < ActiveJob::Base
|
3
|
-
queue_as
|
3
|
+
queue_as { Searchkick.queue_name }
|
4
|
+
|
5
|
+
def perform(class_name:, record_ids:, index_name: nil)
|
6
|
+
# separate routing from id
|
7
|
+
routing = Hash[record_ids.map { |r| r.split(/(?<!\|)\|(?!\|)/, 2).map { |v| v.gsub("||", "|") } }]
|
8
|
+
record_ids = routing.keys
|
4
9
|
|
5
|
-
def perform(class_name:, record_ids:)
|
6
10
|
klass = class_name.constantize
|
7
11
|
scope = Searchkick.load_records(klass, record_ids)
|
8
12
|
scope = scope.search_import if scope.respond_to?(:search_import)
|
@@ -10,10 +14,19 @@ module Searchkick
|
|
10
14
|
|
11
15
|
# determine which records to delete
|
12
16
|
delete_ids = record_ids - records.map { |r| r.id.to_s }
|
13
|
-
delete_records = delete_ids.map
|
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
|
24
|
+
end
|
25
|
+
m
|
26
|
+
end
|
14
27
|
|
15
28
|
# bulk reindex
|
16
|
-
index = klass.searchkick_index
|
29
|
+
index = klass.searchkick_index(name: index_name)
|
17
30
|
Searchkick.callbacks(:bulk) do
|
18
31
|
index.bulk_index(records) if records.any?
|
19
32
|
index.bulk_delete(delete_records) if delete_records.any?
|
@@ -1,22 +1,30 @@
|
|
1
1
|
module Searchkick
|
2
2
|
class ProcessQueueJob < ActiveJob::Base
|
3
|
-
queue_as
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
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
|