searchkick 5.0.2 → 5.4.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 +72 -0
- data/LICENSE.txt +1 -1
- data/README.md +236 -96
- data/lib/searchkick/index.rb +6 -3
- data/lib/searchkick/index_options.rb +77 -2
- data/lib/searchkick/log_subscriber.rb +3 -3
- data/lib/searchkick/middleware.rb +8 -1
- data/lib/searchkick/model.rb +5 -5
- data/lib/searchkick/query.rb +154 -13
- data/lib/searchkick/reindex_queue.rb +12 -7
- data/lib/searchkick/relation.rb +137 -2
- data/lib/searchkick/relation_indexer.rb +12 -7
- data/lib/searchkick/reranking.rb +28 -0
- data/lib/searchkick/results.rb +3 -1
- data/lib/searchkick/script.rb +11 -0
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick/where.rb +11 -0
- data/lib/searchkick.rb +42 -22
- metadata +9 -6
data/lib/searchkick/relation.rb
CHANGED
@@ -8,6 +8,9 @@ module Searchkick
|
|
8
8
|
delegate :body, :params, to: :query
|
9
9
|
delegate_missing_to :private_execute
|
10
10
|
|
11
|
+
attr_reader :model
|
12
|
+
alias_method :klass, :model
|
13
|
+
|
11
14
|
def initialize(model, term = "*", **options)
|
12
15
|
@model = model
|
13
16
|
@term = term
|
@@ -26,20 +29,22 @@ module Searchkick
|
|
26
29
|
|
27
30
|
def execute
|
28
31
|
Searchkick.warn("The execute method is no longer needed")
|
29
|
-
|
30
|
-
self
|
32
|
+
load
|
31
33
|
end
|
32
34
|
|
35
|
+
# experimental
|
33
36
|
def limit(value)
|
34
37
|
clone.limit!(value)
|
35
38
|
end
|
36
39
|
|
40
|
+
# experimental
|
37
41
|
def limit!(value)
|
38
42
|
check_loaded
|
39
43
|
@options[:limit] = value
|
40
44
|
self
|
41
45
|
end
|
42
46
|
|
47
|
+
# experimental
|
43
48
|
def offset(value = NO_DEFAULT_VALUE)
|
44
49
|
# TODO remove in Searchkick 6
|
45
50
|
if value == NO_DEFAULT_VALUE
|
@@ -49,22 +54,26 @@ module Searchkick
|
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
57
|
+
# experimental
|
52
58
|
def offset!(value)
|
53
59
|
check_loaded
|
54
60
|
@options[:offset] = value
|
55
61
|
self
|
56
62
|
end
|
57
63
|
|
64
|
+
# experimental
|
58
65
|
def page(value)
|
59
66
|
clone.page!(value)
|
60
67
|
end
|
61
68
|
|
69
|
+
# experimental
|
62
70
|
def page!(value)
|
63
71
|
check_loaded
|
64
72
|
@options[:page] = value
|
65
73
|
self
|
66
74
|
end
|
67
75
|
|
76
|
+
# experimental
|
68
77
|
def per_page(value = NO_DEFAULT_VALUE)
|
69
78
|
# TODO remove in Searchkick 6
|
70
79
|
if value == NO_DEFAULT_VALUE
|
@@ -74,24 +83,139 @@ module Searchkick
|
|
74
83
|
end
|
75
84
|
end
|
76
85
|
|
86
|
+
# experimental
|
77
87
|
def per_page!(value)
|
78
88
|
check_loaded
|
79
89
|
@options[:per_page] = value
|
80
90
|
self
|
81
91
|
end
|
82
92
|
|
93
|
+
# experimental
|
94
|
+
def where(value = NO_DEFAULT_VALUE)
|
95
|
+
if value == NO_DEFAULT_VALUE
|
96
|
+
Where.new(self)
|
97
|
+
else
|
98
|
+
clone.where!(value)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# experimental
|
103
|
+
def where!(value)
|
104
|
+
check_loaded
|
105
|
+
if @options[:where]
|
106
|
+
@options[:where] = {_and: [@options[:where], ensure_permitted(value)]}
|
107
|
+
else
|
108
|
+
@options[:where] = ensure_permitted(value)
|
109
|
+
end
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
# experimental
|
114
|
+
def rewhere(value)
|
115
|
+
clone.rewhere!(value)
|
116
|
+
end
|
117
|
+
|
118
|
+
# experimental
|
119
|
+
def rewhere!(value)
|
120
|
+
check_loaded
|
121
|
+
@options[:where] = ensure_permitted(value)
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
# experimental
|
126
|
+
def order(*values)
|
127
|
+
clone.order!(*values)
|
128
|
+
end
|
129
|
+
|
130
|
+
# experimental
|
131
|
+
def order!(*values)
|
132
|
+
values = values.first if values.size == 1 && values.first.is_a?(Array)
|
133
|
+
check_loaded
|
134
|
+
(@options[:order] ||= []).concat(values)
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
# experimental
|
139
|
+
def reorder(*values)
|
140
|
+
clone.reorder!(*values)
|
141
|
+
end
|
142
|
+
|
143
|
+
# experimental
|
144
|
+
def reorder!(*values)
|
145
|
+
check_loaded
|
146
|
+
@options[:order] = values
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
# experimental
|
151
|
+
def select(*values, &block)
|
152
|
+
if block_given?
|
153
|
+
private_execute.select(*values, &block)
|
154
|
+
else
|
155
|
+
clone.select!(*values)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# experimental
|
160
|
+
def select!(*values)
|
161
|
+
check_loaded
|
162
|
+
(@options[:select] ||= []).concat(values)
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
# experimental
|
167
|
+
def reselect(*values)
|
168
|
+
clone.reselect!(*values)
|
169
|
+
end
|
170
|
+
|
171
|
+
# experimental
|
172
|
+
def reselect!(*values)
|
173
|
+
check_loaded
|
174
|
+
@options[:select] = values
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
# experimental
|
179
|
+
def includes(*values)
|
180
|
+
clone.includes!(*values)
|
181
|
+
end
|
182
|
+
|
183
|
+
# experimental
|
184
|
+
def includes!(*values)
|
185
|
+
check_loaded
|
186
|
+
(@options[:includes] ||= []).concat(values)
|
187
|
+
self
|
188
|
+
end
|
189
|
+
|
190
|
+
# experimental
|
83
191
|
def only(*keys)
|
84
192
|
Relation.new(@model, @term, **@options.slice(*keys))
|
85
193
|
end
|
86
194
|
|
195
|
+
# experimental
|
87
196
|
def except(*keys)
|
88
197
|
Relation.new(@model, @term, **@options.except(*keys))
|
89
198
|
end
|
90
199
|
|
200
|
+
# experimental
|
201
|
+
def load
|
202
|
+
private_execute
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
91
206
|
def loaded?
|
92
207
|
!@execute.nil?
|
93
208
|
end
|
94
209
|
|
210
|
+
def respond_to_missing?(method_name, include_all)
|
211
|
+
Results.new(nil, nil, nil).respond_to?(method_name, include_all) || super
|
212
|
+
end
|
213
|
+
|
214
|
+
# TODO uncomment in 6.0
|
215
|
+
# def to_yaml
|
216
|
+
# private_execute.to_a.to_yaml
|
217
|
+
# end
|
218
|
+
|
95
219
|
private
|
96
220
|
|
97
221
|
def private_execute
|
@@ -108,5 +232,16 @@ module Searchkick
|
|
108
232
|
# reset query since options will change
|
109
233
|
@query = nil
|
110
234
|
end
|
235
|
+
|
236
|
+
# provides *very* basic protection from unfiltered parameters
|
237
|
+
# this is not meant to be comprehensive and may be expanded in the future
|
238
|
+
def ensure_permitted(obj)
|
239
|
+
obj.to_h
|
240
|
+
end
|
241
|
+
|
242
|
+
def initialize_copy(other)
|
243
|
+
super
|
244
|
+
@execute = nil
|
245
|
+
end
|
111
246
|
end
|
112
247
|
end
|
@@ -14,12 +14,17 @@ module Searchkick
|
|
14
14
|
relation = relation.search_import
|
15
15
|
end
|
16
16
|
|
17
|
-
# remove unneeded loading for async
|
18
|
-
if mode == :async
|
17
|
+
# remove unneeded loading for async and queue
|
18
|
+
if mode == :async || mode == :queue
|
19
19
|
if relation.respond_to?(:primary_key)
|
20
|
-
relation = relation.
|
20
|
+
relation = relation.except(:includes, :preload)
|
21
|
+
unless mode == :queue && relation.klass.method_defined?(:search_routing)
|
22
|
+
relation = relation.except(:select).select(relation.primary_key)
|
23
|
+
end
|
21
24
|
elsif relation.respond_to?(:only)
|
22
|
-
|
25
|
+
unless mode == :queue && relation.klass.method_defined?(:search_routing)
|
26
|
+
relation = relation.only(:_id)
|
27
|
+
end
|
23
28
|
end
|
24
29
|
end
|
25
30
|
|
@@ -42,11 +47,11 @@ module Searchkick
|
|
42
47
|
end
|
43
48
|
|
44
49
|
def batches_left
|
45
|
-
Searchkick.with_redis { |r| r.
|
50
|
+
Searchkick.with_redis { |r| r.call("SCARD", batches_key) }
|
46
51
|
end
|
47
52
|
|
48
53
|
def batch_completed(batch_id)
|
49
|
-
Searchkick.with_redis { |r| r.
|
54
|
+
Searchkick.with_redis { |r| r.call("SREM", batches_key, [batch_id]) }
|
50
55
|
end
|
51
56
|
|
52
57
|
private
|
@@ -134,7 +139,7 @@ module Searchkick
|
|
134
139
|
end
|
135
140
|
|
136
141
|
def batch_job(class_name, batch_id, record_ids)
|
137
|
-
Searchkick.with_redis { |r| r.
|
142
|
+
Searchkick.with_redis { |r| r.call("SADD", batches_key, [batch_id]) }
|
138
143
|
Searchkick::BulkReindexJob.perform_later(
|
139
144
|
class_name: class_name,
|
140
145
|
index_name: index.name,
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Searchkick
|
2
|
+
module Reranking
|
3
|
+
def self.rrf(first_ranking, *rankings, k: 60)
|
4
|
+
rankings.unshift(first_ranking)
|
5
|
+
rankings.map!(&:to_ary)
|
6
|
+
|
7
|
+
ranks = []
|
8
|
+
results = []
|
9
|
+
rankings.each do |ranking|
|
10
|
+
ranks << ranking.map.with_index.to_h { |v, i| [v, i + 1] }
|
11
|
+
results.concat(ranking)
|
12
|
+
end
|
13
|
+
|
14
|
+
results =
|
15
|
+
results.uniq.map do |result|
|
16
|
+
score =
|
17
|
+
ranks.sum do |rank|
|
18
|
+
r = rank[result]
|
19
|
+
r ? 1.0 / (k + r) : 0.0
|
20
|
+
end
|
21
|
+
|
22
|
+
{result: result, score: score}
|
23
|
+
end
|
24
|
+
|
25
|
+
results.sort_by { |v| -v[:score] }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/searchkick/results.rb
CHANGED
@@ -3,6 +3,7 @@ module Searchkick
|
|
3
3
|
include Enumerable
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
+
# TODO remove klass and options in 6.0
|
6
7
|
attr_reader :klass, :response, :options
|
7
8
|
|
8
9
|
def_delegators :results, :each, :any?, :empty?, :size, :length, :slice, :[], :to_ary
|
@@ -13,6 +14,7 @@ module Searchkick
|
|
13
14
|
@options = options
|
14
15
|
end
|
15
16
|
|
17
|
+
# TODO make private in 6.0
|
16
18
|
def results
|
17
19
|
@results ||= with_hit.map(&:first)
|
18
20
|
end
|
@@ -302,7 +304,7 @@ module Searchkick
|
|
302
304
|
def build_hits
|
303
305
|
@build_hits ||= begin
|
304
306
|
if missing_records.any?
|
305
|
-
Searchkick.warn("Records in search index do not exist in database: #{missing_records.map { |v| v[:id] }.join(", ")}")
|
307
|
+
Searchkick.warn("Records in search index do not exist in database: #{missing_records.map { |v| "#{Array(v[:model]).map(&:model_name).sort.join("/")} #{v[:id]}" }.join(", ")}")
|
306
308
|
end
|
307
309
|
with_hit_and_missing_records[0]
|
308
310
|
end
|
data/lib/searchkick/version.rb
CHANGED
data/lib/searchkick.rb
CHANGED
@@ -10,26 +10,29 @@ require "hashie"
|
|
10
10
|
require "forwardable"
|
11
11
|
|
12
12
|
# modules
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
13
|
+
require_relative "searchkick/controller_runtime"
|
14
|
+
require_relative "searchkick/index"
|
15
|
+
require_relative "searchkick/index_cache"
|
16
|
+
require_relative "searchkick/index_options"
|
17
|
+
require_relative "searchkick/indexer"
|
18
|
+
require_relative "searchkick/hash_wrapper"
|
19
|
+
require_relative "searchkick/log_subscriber"
|
20
|
+
require_relative "searchkick/model"
|
21
|
+
require_relative "searchkick/multi_search"
|
22
|
+
require_relative "searchkick/query"
|
23
|
+
require_relative "searchkick/reindex_queue"
|
24
|
+
require_relative "searchkick/record_data"
|
25
|
+
require_relative "searchkick/record_indexer"
|
26
|
+
require_relative "searchkick/relation"
|
27
|
+
require_relative "searchkick/relation_indexer"
|
28
|
+
require_relative "searchkick/reranking"
|
29
|
+
require_relative "searchkick/results"
|
30
|
+
require_relative "searchkick/script"
|
31
|
+
require_relative "searchkick/version"
|
32
|
+
require_relative "searchkick/where"
|
30
33
|
|
31
34
|
# integrations
|
32
|
-
|
35
|
+
require_relative "searchkick/railtie" if defined?(Rails)
|
33
36
|
|
34
37
|
module Searchkick
|
35
38
|
# requires faraday
|
@@ -134,11 +137,21 @@ module Searchkick
|
|
134
137
|
@opensearch
|
135
138
|
end
|
136
139
|
|
137
|
-
|
138
|
-
|
140
|
+
# TODO always check true version in Searchkick 6
|
141
|
+
def self.server_below?(version, true_version = false)
|
142
|
+
server_version = !true_version && opensearch? ? "7.10.2" : self.server_version
|
139
143
|
Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
|
140
144
|
end
|
141
145
|
|
146
|
+
# private
|
147
|
+
def self.knn_support?
|
148
|
+
if opensearch?
|
149
|
+
!server_below?("2.4.0", true)
|
150
|
+
else
|
151
|
+
!server_below?("8.6.0")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
142
155
|
def self.search(term = "*", model: nil, **options, &block)
|
143
156
|
options = options.dup
|
144
157
|
klass = model
|
@@ -180,13 +193,20 @@ module Searchkick
|
|
180
193
|
queries = queries.map { |q| q.send(:query) }
|
181
194
|
event = {
|
182
195
|
name: "Multi Search",
|
183
|
-
body: queries.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
|
196
|
+
body: queries.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
|
184
197
|
}
|
185
198
|
ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
|
186
199
|
MultiSearch.new(queries).perform
|
187
200
|
end
|
188
201
|
end
|
189
202
|
|
203
|
+
# script
|
204
|
+
|
205
|
+
# experimental
|
206
|
+
def self.script(source, **options)
|
207
|
+
Script.new(source, **options)
|
208
|
+
end
|
209
|
+
|
190
210
|
# callbacks
|
191
211
|
|
192
212
|
def self.enable_callbacks
|
@@ -283,7 +303,7 @@ module Searchkick
|
|
283
303
|
relation
|
284
304
|
end
|
285
305
|
|
286
|
-
#
|
306
|
+
# public (for reindexing conversions)
|
287
307
|
def self.load_model(class_name, allow_child: false)
|
288
308
|
model = class_name.safe_constantize
|
289
309
|
raise Error, "Could not find class: #{class_name}" unless model
|
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: 5.0
|
4
|
+
version: 5.4.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:
|
11
|
+
date: 2024-09-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '6.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: hashie
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -69,8 +69,11 @@ files:
|
|
69
69
|
- lib/searchkick/reindex_v2_job.rb
|
70
70
|
- lib/searchkick/relation.rb
|
71
71
|
- lib/searchkick/relation_indexer.rb
|
72
|
+
- lib/searchkick/reranking.rb
|
72
73
|
- lib/searchkick/results.rb
|
74
|
+
- lib/searchkick/script.rb
|
73
75
|
- lib/searchkick/version.rb
|
76
|
+
- lib/searchkick/where.rb
|
74
77
|
- lib/tasks/searchkick.rake
|
75
78
|
homepage: https://github.com/ankane/searchkick
|
76
79
|
licenses:
|
@@ -84,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
87
|
requirements:
|
85
88
|
- - ">="
|
86
89
|
- !ruby/object:Gem::Version
|
87
|
-
version: '
|
90
|
+
version: '3.1'
|
88
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
92
|
requirements:
|
90
93
|
- - ">="
|
91
94
|
- !ruby/object:Gem::Version
|
92
95
|
version: '0'
|
93
96
|
requirements: []
|
94
|
-
rubygems_version: 3.
|
97
|
+
rubygems_version: 3.5.11
|
95
98
|
signing_key:
|
96
99
|
specification_version: 4
|
97
100
|
summary: Intelligent search made easy with Rails and Elasticsearch or OpenSearch
|