searchkick 3.1.2 → 4.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ module Searckick
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load "tasks/searchkick.rake"
5
+ end
6
+ end
7
+ end
@@ -34,23 +34,18 @@ module Searchkick
34
34
  index.klass_document_type(record.class, ignore_type)
35
35
  end
36
36
 
37
- # memoize
38
- def self.routing_key
39
- @routing_key ||= Searchkick.server_below?("6.0.0") ? :_routing : :routing
40
- end
41
-
42
- private
43
-
44
37
  def record_data
45
38
  data = {
46
39
  _index: index.name,
47
- _id: search_id,
48
- _type: document_type
40
+ _id: search_id
49
41
  }
50
- data[self.class.routing_key] = record.search_routing if record.respond_to?(:search_routing)
42
+ data[:_type] = document_type if Searchkick.server_below7?
43
+ data[:routing] = record.search_routing if record.respond_to?(:search_routing)
51
44
  data
52
45
  end
53
46
 
47
+ private
48
+
54
49
  def search_data(method_name = nil)
55
50
  partial_reindex = !method_name.nil?
56
51
 
@@ -19,71 +19,24 @@ module Searchkick
19
19
  @results ||= with_hit.map(&:first)
20
20
  end
21
21
 
22
+ # TODO return enumerator like with_score
22
23
  def with_hit
23
24
  @with_hit ||= begin
24
- if options[:load]
25
- # results can have different types
26
- results = {}
27
-
28
- hits.group_by { |hit, _| hit["_type"] }.each do |type, grouped_hits|
29
- klass = (!options[:index_name] && @klass) || type.camelize.constantize
30
- results[type] = results_query(klass, grouped_hits).to_a.index_by { |r| r.id.to_s }
31
- end
32
-
33
- missing_ids = []
34
-
35
- # sort
36
- results =
37
- hits.map do |hit|
38
- result = results[hit["_type"]][hit["_id"].to_s]
39
- if result && !(options[:load].is_a?(Hash) && options[:load][:dumpable])
40
- if (hit["highlight"] || options[:highlight]) && !result.respond_to?(:search_highlights)
41
- highlights = hit_highlights(hit)
42
- result.define_singleton_method(:search_highlights) do
43
- highlights
44
- end
45
- end
46
- end
47
- [result, hit]
48
- end.select do |result, hit|
49
- missing_ids << hit["_id"] unless result
50
- result
51
- end
52
-
53
- if missing_ids.any?
54
- warn "[searchkick] WARNING: Records in search index do not exist in database: #{missing_ids.join(", ")}"
55
- end
56
-
57
- results
58
- else
59
- hits.map do |hit|
60
- result =
61
- if hit["_source"]
62
- hit.except("_source").merge(hit["_source"])
63
- elsif hit["fields"]
64
- hit.except("fields").merge(hit["fields"])
65
- else
66
- hit
67
- end
68
-
69
- if hit["highlight"] || options[:highlight]
70
- highlight = Hash[hit["highlight"].to_a.map { |k, v| [base_field(k), v.first] }]
71
- options[:highlighted_fields].map { |k| base_field(k) }.each do |k|
72
- result["highlighted_#{k}"] ||= (highlight[k] || result[k])
73
- end
74
- end
75
-
76
- result["id"] ||= result["_id"] # needed for legacy reasons
77
- [HashWrapper.new(result), hit]
78
- end
25
+ if missing_records.any?
26
+ Searchkick.warn("Records in search index do not exist in database: #{missing_records.map { |v| v[:id] }.join(", ")}")
79
27
  end
28
+ with_hit_and_missing_records[0]
80
29
  end
81
30
  end
82
31
 
32
+ def missing_records
33
+ @missing_records ||= with_hit_and_missing_records[1]
34
+ end
35
+
83
36
  def suggestions
84
37
  if response["suggest"]
85
38
  response["suggest"].values.flat_map { |v| v.first["options"] }.sort_by { |o| -o["score"] }.map { |o| o["text"] }.uniq
86
- elsif options[:term] == "*"
39
+ elsif options[:suggest] || options[:term] == "*" # TODO remove 2nd term
87
40
  []
88
41
  else
89
42
  raise "Pass `suggest: true` to the search method for suggestions"
@@ -132,7 +85,13 @@ module Searchkick
132
85
  end
133
86
 
134
87
  def total_count
135
- options[:total_entries] || response["hits"]["total"]
88
+ if options[:total_entries]
89
+ options[:total_entries]
90
+ elsif response["hits"]["total"].is_a?(Hash)
91
+ response["hits"]["total"]["value"]
92
+ else
93
+ response["hits"]["total"]
94
+ end
136
95
  end
137
96
  alias_method :total_entries, :total_count
138
97
 
@@ -194,18 +153,156 @@ module Searchkick
194
153
  end
195
154
  end
196
155
 
156
+ # TODO return enumerator like with_score
197
157
  def with_highlights(multiple: false)
198
158
  with_hit.map do |result, hit|
199
159
  [result, hit_highlights(hit, multiple: multiple)]
200
160
  end
201
161
  end
202
162
 
163
+ def with_score
164
+ return enum_for(:with_score) unless block_given?
165
+
166
+ with_hit.each do |result, hit|
167
+ yield result, hit["_score"]
168
+ end
169
+ end
170
+
203
171
  def misspellings?
204
172
  @options[:misspellings]
205
173
  end
206
174
 
175
+ def scroll_id
176
+ @response["_scroll_id"]
177
+ end
178
+
179
+ def scroll
180
+ raise Searchkick::Error, "Pass `scroll` option to the search method for scrolling" unless scroll_id
181
+
182
+ if block_given?
183
+ records = self
184
+ while records.any?
185
+ yield records
186
+ records = records.scroll
187
+ end
188
+
189
+ records.clear_scroll
190
+ else
191
+ params = {
192
+ scroll: options[:scroll],
193
+ scroll_id: scroll_id
194
+ }
195
+
196
+ begin
197
+ # TODO Active Support notifications for this scroll call
198
+ Searchkick::Results.new(@klass, Searchkick.client.scroll(params), @options)
199
+ rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
200
+ if e.class.to_s =~ /NotFound/ && e.message =~ /search_context_missing_exception/i
201
+ raise Searchkick::Error, "Scroll id has expired"
202
+ else
203
+ raise e
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ def clear_scroll
210
+ begin
211
+ # try to clear scroll
212
+ # not required as scroll will expire
213
+ # but there is a cost to open scrolls
214
+ Searchkick.client.clear_scroll(scroll_id: scroll_id)
215
+ rescue Elasticsearch::Transport::Transport::Error
216
+ # do nothing
217
+ end
218
+ end
219
+
207
220
  private
208
221
 
222
+ def with_hit_and_missing_records
223
+ @with_hit_and_missing_records ||= begin
224
+ missing_records = []
225
+
226
+ if options[:load]
227
+ grouped_hits = hits.group_by { |hit, _| hit["_index"] }
228
+
229
+ # determine models
230
+ index_models = {}
231
+ grouped_hits.each do |index, _|
232
+ models =
233
+ if @klass
234
+ [@klass]
235
+ else
236
+ index_alias = index.split("_")[0..-2].join("_")
237
+ Array((options[:index_mapping] || {})[index_alias])
238
+ end
239
+ raise Searchkick::Error, "Unknown model for index: #{index}" unless models.any?
240
+ index_models[index] = models
241
+ end
242
+
243
+ # fetch results
244
+ results = {}
245
+ grouped_hits.each do |index, index_hits|
246
+ results[index] = {}
247
+ index_models[index].each do |model|
248
+ results[index].merge!(results_query(model, index_hits).to_a.index_by { |r| r.id.to_s })
249
+ end
250
+ end
251
+
252
+ # sort
253
+ results =
254
+ hits.map do |hit|
255
+ result = results[hit["_index"]][hit["_id"].to_s]
256
+ if result && !(options[:load].is_a?(Hash) && options[:load][:dumpable])
257
+ if (hit["highlight"] || options[:highlight]) && !result.respond_to?(:search_highlights)
258
+ highlights = hit_highlights(hit)
259
+ result.define_singleton_method(:search_highlights) do
260
+ highlights
261
+ end
262
+ end
263
+ end
264
+ [result, hit]
265
+ end.select do |result, hit|
266
+ unless result
267
+ models = index_models[hit["_index"]]
268
+ missing_records << {
269
+ id: hit["_id"],
270
+ # may be multiple models for inheritance with child models
271
+ # not ideal to return different types
272
+ # but this situation shouldn't be common
273
+ model: models.size == 1 ? models.first : models
274
+ }
275
+ end
276
+ result
277
+ end
278
+ else
279
+ results =
280
+ hits.map do |hit|
281
+ result =
282
+ if hit["_source"]
283
+ hit.except("_source").merge(hit["_source"])
284
+ elsif hit["fields"]
285
+ hit.except("fields").merge(hit["fields"])
286
+ else
287
+ hit
288
+ end
289
+
290
+ if hit["highlight"] || options[:highlight]
291
+ highlight = Hash[hit["highlight"].to_a.map { |k, v| [base_field(k), v.first] }]
292
+ options[:highlighted_fields].map { |k| base_field(k) }.each do |k|
293
+ result["highlighted_#{k}"] ||= (highlight[k] || result[k])
294
+ end
295
+ end
296
+
297
+ result["id"] ||= result["_id"] # needed for legacy reasons
298
+ [HashWrapper.new(result), hit]
299
+ end
300
+ end
301
+
302
+ [results, missing_records]
303
+ end
304
+ end
305
+
209
306
  def results_query(records, hits)
210
307
  ids = hits.map { |hit| hit["_id"] }
211
308
  if options[:includes] || options[:model_includes]
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "3.1.2"
2
+ VERSION = "4.4.2"
3
3
  end
@@ -0,0 +1,34 @@
1
+ namespace :searchkick do
2
+ desc "reindex a model (specify CLASS)"
3
+ task reindex: :environment do
4
+ class_name = ENV["CLASS"]
5
+ abort "USAGE: rake searchkick:reindex CLASS=Product" unless class_name
6
+
7
+ model = class_name.safe_constantize
8
+ abort "Could not find class: #{class_name}" unless model
9
+ abort "#{class_name} is not a searchkick model" unless Searchkick.models.include?(model)
10
+
11
+ puts "Reindexing #{model.name}..."
12
+ model.reindex
13
+ puts "Reindex successful"
14
+ end
15
+
16
+ namespace :reindex do
17
+ desc "reindex all models"
18
+ task all: :environment do
19
+ # eager load models to populate Searchkick.models
20
+ if Rails.respond_to?(:autoloaders) && Rails.autoloaders.zeitwerk_enabled?
21
+ # fix for https://github.com/rails/rails/issues/37006
22
+ Zeitwerk::Loader.eager_load_all
23
+ else
24
+ Rails.application.eager_load!
25
+ end
26
+
27
+ Searchkick.models.each do |model|
28
+ puts "Reindexing #{model.name}..."
29
+ model.reindex
30
+ end
31
+ puts "Reindex complete"
32
+ end
33
+ end
34
+ 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: 3.1.2
4
+ version: 4.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-28 00:00:00.000000000 Z
11
+ date: 2020-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '5'
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: '4.2'
26
+ version: '5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: elasticsearch
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '5'
33
+ version: '6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '5'
40
+ version: '6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: hashie
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,56 +52,13 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: minitest
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rake
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- description:
55
+ description:
98
56
  email: andrew@chartkick.com
99
57
  executables: []
100
58
  extensions: []
101
59
  extra_rdoc_files: []
102
60
  files:
103
61
  - CHANGELOG.md
104
- - CONTRIBUTING.md
105
62
  - LICENSE.txt
106
63
  - README.md
107
64
  - lib/searchkick.rb
@@ -118,18 +75,19 @@ files:
118
75
  - lib/searchkick/process_batch_job.rb
119
76
  - lib/searchkick/process_queue_job.rb
120
77
  - lib/searchkick/query.rb
78
+ - lib/searchkick/railtie.rb
121
79
  - lib/searchkick/record_data.rb
122
80
  - lib/searchkick/record_indexer.rb
123
81
  - lib/searchkick/reindex_queue.rb
124
82
  - lib/searchkick/reindex_v2_job.rb
125
83
  - lib/searchkick/results.rb
126
- - lib/searchkick/tasks.rb
127
84
  - lib/searchkick/version.rb
85
+ - lib/tasks/searchkick.rake
128
86
  homepage: https://github.com/ankane/searchkick
129
87
  licenses:
130
88
  - MIT
131
89
  metadata: {}
132
- post_install_message:
90
+ post_install_message:
133
91
  rdoc_options: []
134
92
  require_paths:
135
93
  - lib
@@ -137,16 +95,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
95
  requirements:
138
96
  - - ">="
139
97
  - !ruby/object:Gem::Version
140
- version: '2.2'
98
+ version: '2.4'
141
99
  required_rubygems_version: !ruby/object:Gem::Requirement
142
100
  requirements:
143
101
  - - ">="
144
102
  - !ruby/object:Gem::Version
145
103
  version: '0'
146
104
  requirements: []
147
- rubyforge_project:
148
- rubygems_version: 2.7.7
149
- signing_key:
105
+ rubygems_version: 3.1.4
106
+ signing_key:
150
107
  specification_version: 4
151
- summary: Intelligent search made easy
108
+ summary: Intelligent search made easy with Rails and Elasticsearch
152
109
  test_files: []