Commit d5312f12f451c27fcf9570c6627f4823ae25452b

Authored by Andrew Kane
1 parent 13fd0f4e
Exists in allow_missing

Added allow_missing option to reindex

lib/searchkick/bulk_reindex_job.rb
@@ -14,7 +14,8 @@ module Searchkick @@ -14,7 +14,8 @@ module Searchkick
14 relation = Searchkick.load_records(relation, record_ids) 14 relation = Searchkick.load_records(relation, record_ids)
15 relation = relation.search_import if relation.respond_to?(:search_import) 15 relation = relation.search_import if relation.respond_to?(:search_import)
16 16
17 - RecordIndexer.new(index).reindex(relation, mode: :inline, method_name: method_name, full: false) 17 + # TODO support allow_missing
  18 + RecordIndexer.new(index).reindex(relation, mode: :inline, method_name: method_name, allow_missing: false, full: false)
18 RelationIndexer.new(index).batch_completed(batch_id) if batch_id 19 RelationIndexer.new(index).batch_completed(batch_id) if batch_id
19 end 20 end
20 end 21 end
lib/searchkick/index.rb
@@ -163,11 +163,11 @@ module Searchkick @@ -163,11 +163,11 @@ module Searchkick
163 end 163 end
164 alias_method :import, :bulk_index 164 alias_method :import, :bulk_index
165 165
166 - def bulk_update(records, method_name) 166 + def bulk_update(records, method_name, allow_missing: false)
167 return if records.empty? 167 return if records.empty?
168 168
169 notify_bulk(records, "Update") do 169 notify_bulk(records, "Update") do
170 - queue_update(records, method_name) 170 + queue_update(records, method_name, allow_missing: allow_missing)
171 end 171 end
172 end 172 end
173 173
@@ -211,10 +211,10 @@ module Searchkick @@ -211,10 +211,10 @@ module Searchkick
211 211
212 # note: this is designed to be used internally 212 # note: this is designed to be used internally
213 # so it does not check object matches index class 213 # so it does not check object matches index class
214 - def reindex(object, method_name: nil, full: false, **options) 214 + def reindex(object, method_name: nil, allow_missing: false, full: false, **options)
215 if object.is_a?(Array) 215 if object.is_a?(Array)
216 # note: purposefully skip full 216 # note: purposefully skip full
217 - return reindex_records(object, method_name: method_name, **options) 217 + return reindex_records(object, method_name: method_name, allow_missing: allow_missing, **options)
218 end 218 end
219 219
220 if !object.respond_to?(:searchkick_klass) 220 if !object.respond_to?(:searchkick_klass)
@@ -233,7 +233,7 @@ module Searchkick @@ -233,7 +233,7 @@ module Searchkick
233 raise ArgumentError, "unsupported keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any? 233 raise ArgumentError, "unsupported keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?
234 234
235 # import only 235 # import only
236 - import_scope(relation, method_name: method_name, mode: mode) 236 + import_scope(relation, method_name: method_name, mode: mode, allow_missing: allow_missing)
237 self.refresh if refresh 237 self.refresh if refresh
238 true 238 true
239 else 239 else
@@ -322,8 +322,10 @@ module Searchkick @@ -322,8 +322,10 @@ module Searchkick
322 Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| RecordData.new(self, r).delete_data }) 322 Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| RecordData.new(self, r).delete_data })
323 end 323 end
324 324
325 - def queue_update(records, method_name)  
326 - Searchkick.indexer.queue(records.map { |r| RecordData.new(self, r).update_data(method_name) }) 325 + def queue_update(records, method_name, allow_missing:)
  326 + items = records.map { |r| RecordData.new(self, r).update_data(method_name) }
  327 + items.each { |i| i.instance_variable_set(:@allow_missing, true) } if allow_missing
  328 + Searchkick.indexer.queue(items)
327 end 329 end
328 330
329 def relation_indexer 331 def relation_indexer
lib/searchkick/indexer.rb
@@ -24,12 +24,20 @@ module Searchkick @@ -24,12 +24,20 @@ module Searchkick
24 # note: delete does not set error when item not found 24 # note: delete does not set error when item not found
25 first_with_error = response["items"].map do |item| 25 first_with_error = response["items"].map do |item|
26 (item["index"] || item["delete"] || item["update"]) 26 (item["index"] || item["delete"] || item["update"])
27 - end.find { |item| item["error"] }  
28 - raise ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'" 27 + end.find.with_index { |item, i| item["error"] && !allow_missing?(items[i], item["error"]) }
  28 + if first_with_error
  29 + raise ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'"
  30 + end
29 end 31 end
30 32
31 # maybe return response in future 33 # maybe return response in future
32 nil 34 nil
33 end 35 end
  36 +
  37 + private
  38 +
  39 + def allow_missing?(item, error)
  40 + error["type"] == "document_missing_exception" && item.instance_variable_defined?(:@allow_missing)
  41 + end
34 end 42 end
35 end 43 end
lib/searchkick/model.rb
@@ -27,8 +27,8 @@ module Searchkick @@ -27,8 +27,8 @@ module Searchkick
27 mod = Module.new 27 mod = Module.new
28 include(mod) 28 include(mod)
29 mod.module_eval do 29 mod.module_eval do
30 - def reindex(method_name = nil, mode: nil, refresh: false)  
31 - self.class.searchkick_index.reindex([self], method_name: method_name, mode: mode, refresh: refresh, single: true) 30 + def reindex(method_name = nil, mode: nil, refresh: false, allow_missing: false)
  31 + self.class.searchkick_index.reindex([self], method_name: method_name, mode: mode, refresh: refresh, allow_missing: allow_missing, single: true)
32 end unless base.method_defined?(:reindex) 32 end unless base.method_defined?(:reindex)
33 33
34 def similar(**options) 34 def similar(**options)
lib/searchkick/process_batch_job.rb
@@ -14,7 +14,7 @@ module Searchkick @@ -14,7 +14,7 @@ module Searchkick
14 end 14 end
15 15
16 relation = Searchkick.scope(model) 16 relation = Searchkick.scope(model)
17 - RecordIndexer.new(index).reindex_items(relation, items, method_name: nil) 17 + RecordIndexer.new(index).reindex_items(relation, items, method_name: nil, allow_missing: false)
18 end 18 end
19 end 19 end
20 end 20 end
lib/searchkick/record_indexer.rb
@@ -6,7 +6,7 @@ module Searchkick @@ -6,7 +6,7 @@ module Searchkick
6 @index = index 6 @index = index
7 end 7 end
8 8
9 - def reindex(records, mode:, method_name:, full: false, single: false) 9 + def reindex(records, mode:, method_name:, allow_missing:, full: false, single: false)
10 # prevents exists? check if records is a relation 10 # prevents exists? check if records is a relation
11 records = records.to_a 11 records = records.to_a
12 return if records.empty? 12 return if records.empty?
@@ -17,6 +17,10 @@ module Searchkick @@ -17,6 +17,10 @@ module Searchkick
17 raise Error, "Active Job not found" 17 raise Error, "Active Job not found"
18 end 18 end
19 19
  20 + if allow_missing
  21 + raise Error, "Allow missing not supported with async option yet"
  22 + end
  23 +
20 # we could likely combine ReindexV2Job, BulkReindexJob, and ProcessBatchJob 24 # we could likely combine ReindexV2Job, BulkReindexJob, and ProcessBatchJob
21 # but keep them separate for now 25 # but keep them separate for now
22 if single 26 if single
@@ -51,7 +55,7 @@ module Searchkick @@ -51,7 +55,7 @@ module Searchkick
51 index.reindex_queue.push_records(records) 55 index.reindex_queue.push_records(records)
52 when true, :inline 56 when true, :inline
53 index_records, other_records = records.partition { |r| index_record?(r) } 57 index_records, other_records = records.partition { |r| index_record?(r) }
54 - import_inline(index_records, !full ? other_records : [], method_name: method_name, single: single) 58 + import_inline(index_records, !full ? other_records : [], method_name: method_name, allow_missing: allow_missing, single: single)
55 else 59 else
56 raise ArgumentError, "Invalid value for mode" 60 raise ArgumentError, "Invalid value for mode"
57 end 61 end
@@ -60,7 +64,7 @@ module Searchkick @@ -60,7 +64,7 @@ module Searchkick
60 true 64 true
61 end 65 end
62 66
63 - def reindex_items(klass, items, method_name:, single: false) 67 + def reindex_items(klass, items, method_name:, allow_missing:, single: false)
64 routing = items.to_h { |r| [r[:id], r[:routing]] } 68 routing = items.to_h { |r| [r[:id], r[:routing]] }
65 record_ids = routing.keys 69 record_ids = routing.keys
66 70
@@ -76,7 +80,7 @@ module Searchkick @@ -76,7 +80,7 @@ module Searchkick
76 construct_record(klass, id, routing[id]) 80 construct_record(klass, id, routing[id])
77 end 81 end
78 82
79 - import_inline(records, delete_records, method_name: method_name, single: single) 83 + import_inline(records, delete_records, method_name: method_name, allow_missing: allow_missing, single: single)
80 end 84 end
81 85
82 private 86 private
@@ -86,13 +90,13 @@ module Searchkick @@ -86,13 +90,13 @@ module Searchkick
86 end 90 end
87 91
88 # import in single request with retries 92 # import in single request with retries
89 - def import_inline(index_records, delete_records, method_name:, single:) 93 + def import_inline(index_records, delete_records, method_name:, allow_missing:, single:)
90 return if index_records.empty? && delete_records.empty? 94 return if index_records.empty? && delete_records.empty?
91 95
92 maybe_bulk(index_records, delete_records, method_name, single) do 96 maybe_bulk(index_records, delete_records, method_name, single) do
93 if index_records.any? 97 if index_records.any?
94 if method_name 98 if method_name
95 - index.bulk_update(index_records, method_name) 99 + index.bulk_update(index_records, method_name, allow_missing: allow_missing)
96 else 100 else
97 index.bulk_index(index_records) 101 index.bulk_index(index_records)
98 end 102 end
lib/searchkick/reindex_v2_job.rb
@@ -11,7 +11,8 @@ module Searchkick @@ -11,7 +11,8 @@ module Searchkick
11 # but keep for now for backwards compatibility 11 # but keep for now for backwards compatibility
12 model = model.unscoped if model.respond_to?(:unscoped) 12 model = model.unscoped if model.respond_to?(:unscoped)
13 items = [{id: id, routing: routing}] 13 items = [{id: id, routing: routing}]
14 - RecordIndexer.new(index).reindex_items(model, items, method_name: method_name, single: true) 14 + # TODO support allow_missing
  15 + RecordIndexer.new(index).reindex_items(model, items, method_name: method_name, allow_missing: false, single: true)
15 end 16 end
16 end 17 end
17 end 18 end
lib/searchkick/relation_indexer.rb
@@ -6,7 +6,7 @@ module Searchkick @@ -6,7 +6,7 @@ module Searchkick
6 @index = index 6 @index = index
7 end 7 end
8 8
9 - def reindex(relation, mode:, method_name: nil, full: false, resume: false, scope: nil) 9 + def reindex(relation, mode:, method_name: nil, allow_missing: false, full: false, resume: false, scope: nil)
10 # apply scopes 10 # apply scopes
11 if scope 11 if scope
12 relation = relation.send(scope) 12 relation = relation.send(scope)
@@ -32,6 +32,7 @@ module Searchkick @@ -32,6 +32,7 @@ module Searchkick
32 reindex_options = { 32 reindex_options = {
33 mode: mode, 33 mode: mode,
34 method_name: method_name, 34 method_name: method_name,
  35 + allow_missing: allow_missing,
35 full: full 36 full: full
36 } 37 }
37 record_indexer = RecordIndexer.new(index) 38 record_indexer = RecordIndexer.new(index)
test/partial_reindex_test.rb
@@ -71,4 +71,17 @@ class PartialReindexTest < Minitest::Test @@ -71,4 +71,17 @@ class PartialReindexTest < Minitest::Test
71 end 71 end
72 assert_match "document missing", error.message 72 assert_match "document missing", error.message
73 end 73 end
  74 +
  75 + def test_allow_missing
  76 + store [{name: "Hi", color: "Blue"}]
  77 +
  78 + product = Product.first
  79 + Product.search_index.remove(product)
  80 +
  81 + product.reindex(:search_name, allow_missing: true)
  82 + Product.where(id: product.id).reindex(:search_name, allow_missing: true)
  83 + Searchkick.callbacks(:bulk) do
  84 + product.reindex(:search_name, allow_missing: true)
  85 + end
  86 + end
74 end 87 end