tiny-quick-gem 0.0.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 +7 -0
- data/searchkick-6.1.2/CHANGELOG.md +919 -0
- data/searchkick-6.1.2/LICENSE.txt +22 -0
- data/searchkick-6.1.2/README.md +2348 -0
- data/searchkick-6.1.2/lib/searchkick/bulk_reindex_job.rb +19 -0
- data/searchkick-6.1.2/lib/searchkick/controller_runtime.rb +40 -0
- data/searchkick-6.1.2/lib/searchkick/hash_wrapper.rb +41 -0
- data/searchkick-6.1.2/lib/searchkick/index.rb +480 -0
- data/searchkick-6.1.2/lib/searchkick/index_cache.rb +30 -0
- data/searchkick-6.1.2/lib/searchkick/index_options.rb +652 -0
- data/searchkick-6.1.2/lib/searchkick/indexer.rb +43 -0
- data/searchkick-6.1.2/lib/searchkick/log_subscriber.rb +57 -0
- data/searchkick-6.1.2/lib/searchkick/middleware.rb +19 -0
- data/searchkick-6.1.2/lib/searchkick/model.rb +120 -0
- data/searchkick-6.1.2/lib/searchkick/multi_search.rb +46 -0
- data/searchkick-6.1.2/lib/searchkick/process_batch_job.rb +20 -0
- data/searchkick-6.1.2/lib/searchkick/process_queue_job.rb +33 -0
- data/searchkick-6.1.2/lib/searchkick/query.rb +1372 -0
- data/searchkick-6.1.2/lib/searchkick/railtie.rb +7 -0
- data/searchkick-6.1.2/lib/searchkick/record_data.rb +147 -0
- data/searchkick-6.1.2/lib/searchkick/record_indexer.rb +174 -0
- data/searchkick-6.1.2/lib/searchkick/reindex_queue.rb +57 -0
- data/searchkick-6.1.2/lib/searchkick/reindex_v2_job.rb +17 -0
- data/searchkick-6.1.2/lib/searchkick/relation.rb +728 -0
- data/searchkick-6.1.2/lib/searchkick/relation_indexer.rb +184 -0
- data/searchkick-6.1.2/lib/searchkick/reranking.rb +28 -0
- data/searchkick-6.1.2/lib/searchkick/results.rb +359 -0
- data/searchkick-6.1.2/lib/searchkick/script.rb +11 -0
- data/searchkick-6.1.2/lib/searchkick/version.rb +3 -0
- data/searchkick-6.1.2/lib/searchkick/where.rb +11 -0
- data/searchkick-6.1.2/lib/searchkick.rb +388 -0
- data/searchkick-6.1.2/lib/tasks/searchkick.rake +37 -0
- data/tiny-quick-gem.gemspec +12 -0
- metadata +73 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class RecordData
|
|
3
|
+
TYPE_KEYS = ["type", :type]
|
|
4
|
+
|
|
5
|
+
attr_reader :index, :record
|
|
6
|
+
|
|
7
|
+
def initialize(index, record)
|
|
8
|
+
@index = index
|
|
9
|
+
@record = record
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def index_data
|
|
13
|
+
data = record_data
|
|
14
|
+
data[:data] = search_data
|
|
15
|
+
{index: data}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_data(method_name)
|
|
19
|
+
data = record_data
|
|
20
|
+
data[:data] = {doc: search_data(method_name)}
|
|
21
|
+
{update: data}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete_data
|
|
25
|
+
{delete: record_data}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# custom id can be useful for load: false
|
|
29
|
+
def search_id
|
|
30
|
+
id = record.respond_to?(:search_document_id) ? record.search_document_id : record.id
|
|
31
|
+
id.is_a?(Numeric) ? id : id.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def document_type(ignore_type = false)
|
|
35
|
+
index.klass_document_type(record.class, ignore_type)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def record_data
|
|
39
|
+
data = {
|
|
40
|
+
_index: index.name,
|
|
41
|
+
_id: search_id
|
|
42
|
+
}
|
|
43
|
+
data[:routing] = record.search_routing if record.respond_to?(:search_routing)
|
|
44
|
+
data
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def search_data(method_name = nil)
|
|
50
|
+
partial_reindex = !method_name.nil?
|
|
51
|
+
|
|
52
|
+
source = record.send(method_name || :search_data)
|
|
53
|
+
|
|
54
|
+
# conversions
|
|
55
|
+
index.conversions_fields.each do |conversions_field|
|
|
56
|
+
if source[conversions_field]
|
|
57
|
+
source[conversions_field] = source[conversions_field].map { |k, v| {query: k, count: v} }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
index.conversions_v2_fields.each do |conversions_field|
|
|
62
|
+
key = source.key?(conversions_field) ? conversions_field : conversions_field.to_sym
|
|
63
|
+
if !partial_reindex || source[key]
|
|
64
|
+
if index.options[:case_sensitive]
|
|
65
|
+
source[key] =
|
|
66
|
+
(source[key] || {}).reduce(Hash.new(0)) do |memo, (k, v)|
|
|
67
|
+
memo[k.to_s.gsub(".", "*")] += v
|
|
68
|
+
memo
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
source[key] =
|
|
72
|
+
(source[key] || {}).reduce(Hash.new(0)) do |memo, (k, v)|
|
|
73
|
+
memo[k.to_s.downcase.gsub(".", "*")] += v
|
|
74
|
+
memo
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# hack to prevent generator field doesn't exist error
|
|
81
|
+
if !partial_reindex
|
|
82
|
+
index.suggest_fields.each do |field|
|
|
83
|
+
if !source.key?(field) && !source.key?(field.to_sym)
|
|
84
|
+
source[field] = nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# locations
|
|
90
|
+
index.locations_fields.each do |field|
|
|
91
|
+
if source[field]
|
|
92
|
+
if !source[field].is_a?(Hash) && (source[field].first.is_a?(Array) || source[field].first.is_a?(Hash))
|
|
93
|
+
# multiple locations
|
|
94
|
+
source[field] = source[field].map { |a| location_value(a) }
|
|
95
|
+
else
|
|
96
|
+
source[field] = location_value(source[field])
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if index.options[:inheritance]
|
|
102
|
+
if !TYPE_KEYS.any? { |tk| source.key?(tk) }
|
|
103
|
+
source[:type] = document_type(true)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
cast_big_decimal(source)
|
|
108
|
+
|
|
109
|
+
source
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def location_value(value)
|
|
113
|
+
if value.is_a?(Array)
|
|
114
|
+
value.map(&:to_f).reverse
|
|
115
|
+
elsif value.is_a?(Hash)
|
|
116
|
+
{lat: value[:lat].to_f, lon: value[:lon].to_f}
|
|
117
|
+
else
|
|
118
|
+
value
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# change all BigDecimal values to floats due to
|
|
123
|
+
# https://github.com/rails/rails/issues/6033
|
|
124
|
+
# possible loss of precision :/
|
|
125
|
+
def cast_big_decimal(obj)
|
|
126
|
+
case obj
|
|
127
|
+
when BigDecimal
|
|
128
|
+
obj.to_f
|
|
129
|
+
when Hash
|
|
130
|
+
obj.each do |k, v|
|
|
131
|
+
# performance
|
|
132
|
+
if v.is_a?(BigDecimal)
|
|
133
|
+
obj[k] = v.to_f
|
|
134
|
+
elsif v.is_a?(Enumerable)
|
|
135
|
+
obj[k] = cast_big_decimal(v)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
when Enumerable
|
|
139
|
+
obj.map do |v|
|
|
140
|
+
cast_big_decimal(v)
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
obj
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class RecordIndexer
|
|
3
|
+
attr_reader :index
|
|
4
|
+
|
|
5
|
+
def initialize(index)
|
|
6
|
+
@index = index
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def reindex(records, mode:, method_name:, ignore_missing:, full: false, single: false, job_options: nil)
|
|
10
|
+
# prevents exists? check if records is a relation
|
|
11
|
+
records = records.to_a
|
|
12
|
+
return if records.empty?
|
|
13
|
+
|
|
14
|
+
case mode
|
|
15
|
+
when :async
|
|
16
|
+
unless defined?(ActiveJob)
|
|
17
|
+
raise Error, "Active Job not found"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
job_options ||= {}
|
|
21
|
+
|
|
22
|
+
# only add if set for backwards compatibility
|
|
23
|
+
extra_options = {}
|
|
24
|
+
if ignore_missing
|
|
25
|
+
extra_options[:ignore_missing] = ignore_missing
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# we could likely combine ReindexV2Job, BulkReindexJob, and ProcessBatchJob
|
|
29
|
+
# but keep them separate for now
|
|
30
|
+
if single
|
|
31
|
+
record = records.first
|
|
32
|
+
|
|
33
|
+
# always pass routing in case record is deleted
|
|
34
|
+
# before the async job runs
|
|
35
|
+
if record.respond_to?(:search_routing)
|
|
36
|
+
routing = record.search_routing
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Searchkick::ReindexV2Job.set(**job_options).perform_later(
|
|
40
|
+
record.class.name,
|
|
41
|
+
record.id.to_s,
|
|
42
|
+
method_name ? method_name.to_s : nil,
|
|
43
|
+
routing: routing,
|
|
44
|
+
index_name: index.name,
|
|
45
|
+
**extra_options
|
|
46
|
+
)
|
|
47
|
+
else
|
|
48
|
+
Searchkick::BulkReindexJob.set(**job_options).perform_later(
|
|
49
|
+
class_name: records.first.class.searchkick_options[:class_name],
|
|
50
|
+
record_ids: records.map { |r| r.id.to_s },
|
|
51
|
+
index_name: index.name,
|
|
52
|
+
method_name: method_name ? method_name.to_s : nil,
|
|
53
|
+
**extra_options
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
when :queue
|
|
57
|
+
if method_name
|
|
58
|
+
raise Error, "Partial reindex not supported with queue option"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
index.reindex_queue.push_records(records)
|
|
62
|
+
when true, :inline
|
|
63
|
+
index_records, other_records = records.partition { |r| index_record?(r) }
|
|
64
|
+
import_inline(index_records, !full ? other_records : [], method_name: method_name, ignore_missing: ignore_missing, single: single)
|
|
65
|
+
else
|
|
66
|
+
raise ArgumentError, "Invalid value for mode"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# return true like model and relation reindex for now
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def reindex_items(klass, items, method_name:, ignore_missing:, single: false)
|
|
74
|
+
routing = items.to_h { |r| [r[:id], r[:routing]] }
|
|
75
|
+
record_ids = routing.keys
|
|
76
|
+
|
|
77
|
+
relation = Searchkick.load_records(klass, record_ids)
|
|
78
|
+
# call search_import even for single records for nested associations
|
|
79
|
+
relation = relation.search_import if relation.respond_to?(:search_import)
|
|
80
|
+
records = relation.select(&:should_index?)
|
|
81
|
+
|
|
82
|
+
# determine which records to delete
|
|
83
|
+
delete_ids = record_ids - records.map { |r| r.id.to_s }
|
|
84
|
+
delete_records =
|
|
85
|
+
delete_ids.map do |id|
|
|
86
|
+
construct_record(klass, id, routing[id])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
import_inline(records, delete_records, method_name: method_name, ignore_missing: ignore_missing, single: single)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def index_record?(record)
|
|
95
|
+
record.persisted? && !record.destroyed? && record.should_index?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# import in single request with retries
|
|
99
|
+
def import_inline(index_records, delete_records, method_name:, ignore_missing:, single:)
|
|
100
|
+
return if index_records.empty? && delete_records.empty?
|
|
101
|
+
|
|
102
|
+
maybe_bulk(index_records, delete_records, method_name, single) do
|
|
103
|
+
if index_records.any?
|
|
104
|
+
if method_name
|
|
105
|
+
index.bulk_update(index_records, method_name, ignore_missing: ignore_missing)
|
|
106
|
+
else
|
|
107
|
+
index.bulk_index(index_records)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if delete_records.any?
|
|
112
|
+
index.bulk_delete(delete_records)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def maybe_bulk(index_records, delete_records, method_name, single)
|
|
118
|
+
if Searchkick.callbacks_value == :bulk
|
|
119
|
+
yield
|
|
120
|
+
else
|
|
121
|
+
# set action and data
|
|
122
|
+
action =
|
|
123
|
+
if single && index_records.empty?
|
|
124
|
+
"Remove"
|
|
125
|
+
elsif method_name
|
|
126
|
+
"Update"
|
|
127
|
+
else
|
|
128
|
+
single ? "Store" : "Import"
|
|
129
|
+
end
|
|
130
|
+
record = index_records.first || delete_records.first
|
|
131
|
+
name = record.class.searchkick_klass.name
|
|
132
|
+
message = lambda do |event|
|
|
133
|
+
event[:name] = "#{name} #{action}"
|
|
134
|
+
if single
|
|
135
|
+
event[:id] = index.search_id(record)
|
|
136
|
+
else
|
|
137
|
+
event[:count] = index_records.size + delete_records.size
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
with_retries do
|
|
142
|
+
Searchkick.callbacks(:bulk, message: message) do
|
|
143
|
+
yield
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def construct_record(klass, id, routing)
|
|
150
|
+
record = klass.new
|
|
151
|
+
record.id = id
|
|
152
|
+
if routing
|
|
153
|
+
record.define_singleton_method(:search_routing) do
|
|
154
|
+
routing
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
record
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def with_retries
|
|
161
|
+
retries = 0
|
|
162
|
+
|
|
163
|
+
begin
|
|
164
|
+
yield
|
|
165
|
+
rescue Faraday::ClientError => e
|
|
166
|
+
if retries < 1
|
|
167
|
+
retries += 1
|
|
168
|
+
retry
|
|
169
|
+
end
|
|
170
|
+
raise e
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class ReindexQueue
|
|
3
|
+
attr_reader :name
|
|
4
|
+
|
|
5
|
+
def initialize(name)
|
|
6
|
+
@name = name
|
|
7
|
+
|
|
8
|
+
raise Error, "Searchkick.redis not set" unless Searchkick.redis
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# supports single and multiple ids
|
|
12
|
+
def push(record_ids)
|
|
13
|
+
Searchkick.with_redis { |r| r.call("LPUSH", redis_key, record_ids) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def push_records(records)
|
|
17
|
+
record_ids =
|
|
18
|
+
records.map do |record|
|
|
19
|
+
# always pass routing in case record is deleted
|
|
20
|
+
# before the queue job runs
|
|
21
|
+
if record.respond_to?(:search_routing)
|
|
22
|
+
routing = record.search_routing
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# escape pipe with double pipe
|
|
26
|
+
value = escape(record.id.to_s)
|
|
27
|
+
value = "#{value}|#{escape(routing)}" if routing
|
|
28
|
+
value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
push(record_ids)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# TODO use reliable queuing
|
|
35
|
+
def reserve(limit: 1000)
|
|
36
|
+
Searchkick.with_redis { |r| r.call("RPOP", redis_key, limit) }.to_a
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def clear
|
|
40
|
+
Searchkick.with_redis { |r| r.call("DEL", redis_key) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def length
|
|
44
|
+
Searchkick.with_redis { |r| r.call("LLEN", redis_key) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def redis_key
|
|
50
|
+
"searchkick:reindex_queue:#{name}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def escape(value)
|
|
54
|
+
value.to_s.gsub("|", "||")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Searchkick
|
|
2
|
+
class ReindexV2Job < Searchkick.parent_job.constantize
|
|
3
|
+
queue_as { Searchkick.queue_name }
|
|
4
|
+
|
|
5
|
+
def perform(class_name, id, method_name = nil, routing: nil, index_name: nil, ignore_missing: nil)
|
|
6
|
+
model = Searchkick.load_model(class_name, allow_child: true)
|
|
7
|
+
index = model.searchkick_index(name: index_name)
|
|
8
|
+
# use should_index? to decide whether to index (not default scope)
|
|
9
|
+
# just like saving inline
|
|
10
|
+
# could use Searchkick.scope() in future
|
|
11
|
+
# but keep for now for backwards compatibility
|
|
12
|
+
model = model.unscoped if model.respond_to?(:unscoped)
|
|
13
|
+
items = [{id: id, routing: routing}]
|
|
14
|
+
RecordIndexer.new(index).reindex_items(model, items, method_name: method_name, ignore_missing: ignore_missing, single: true)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|