searchkick 4.4.0 → 5.3.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 +4 -4
- data/CHANGELOG.md +160 -3
- data/LICENSE.txt +1 -1
- data/README.md +567 -421
- data/lib/searchkick/bulk_reindex_job.rb +12 -8
- data/lib/searchkick/controller_runtime.rb +40 -0
- data/lib/searchkick/index.rb +167 -74
- data/lib/searchkick/index_cache.rb +30 -0
- data/lib/searchkick/index_options.rb +465 -404
- data/lib/searchkick/indexer.rb +15 -8
- data/lib/searchkick/log_subscriber.rb +57 -0
- data/lib/searchkick/middleware.rb +9 -2
- data/lib/searchkick/model.rb +50 -51
- data/lib/searchkick/process_batch_job.rb +9 -25
- data/lib/searchkick/process_queue_job.rb +4 -3
- data/lib/searchkick/query.rb +106 -77
- data/lib/searchkick/record_data.rb +1 -1
- data/lib/searchkick/record_indexer.rb +136 -51
- data/lib/searchkick/reindex_queue.rb +51 -9
- data/lib/searchkick/reindex_v2_job.rb +10 -34
- data/lib/searchkick/relation.rb +247 -0
- data/lib/searchkick/relation_indexer.rb +155 -0
- data/lib/searchkick/results.rb +131 -96
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick/where.rb +11 -0
- data/lib/searchkick.rb +202 -96
- data/lib/tasks/searchkick.rake +14 -10
- metadata +18 -85
- data/CONTRIBUTING.md +0 -53
- data/lib/searchkick/bulk_indexer.rb +0 -173
- data/lib/searchkick/logging.rb +0 -246
@@ -2,16 +2,20 @@ module Searchkick
|
|
2
2
|
class BulkReindexJob < ActiveJob::Base
|
3
3
|
queue_as { Searchkick.queue_name }
|
4
4
|
|
5
|
+
# TODO remove min_id and max_id in Searchkick 6
|
5
6
|
def perform(class_name:, record_ids: nil, index_name: nil, method_name: nil, batch_id: nil, min_id: nil, max_id: nil)
|
6
|
-
|
7
|
-
index =
|
7
|
+
model = Searchkick.load_model(class_name)
|
8
|
+
index = model.searchkick_index(name: index_name)
|
9
|
+
|
10
|
+
# legacy
|
8
11
|
record_ids ||= min_id..max_id
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
)
|
12
|
+
|
13
|
+
relation = Searchkick.scope(model)
|
14
|
+
relation = Searchkick.load_records(relation, record_ids)
|
15
|
+
relation = relation.search_import if relation.respond_to?(:search_import)
|
16
|
+
|
17
|
+
RecordIndexer.new(index).reindex(relation, mode: :inline, method_name: method_name, full: false)
|
18
|
+
RelationIndexer.new(index).batch_completed(batch_id) if batch_id
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# based on https://gist.github.com/mnutt/566725
|
2
|
+
module Searchkick
|
3
|
+
module ControllerRuntime
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
attr_internal :searchkick_runtime
|
9
|
+
|
10
|
+
def process_action(action, *args)
|
11
|
+
# We also need to reset the runtime before each action
|
12
|
+
# because of queries in middleware or in cases we are streaming
|
13
|
+
# and it won't be cleaned up by the method below.
|
14
|
+
Searchkick::LogSubscriber.reset_runtime
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def cleanup_view_runtime
|
19
|
+
searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime
|
20
|
+
runtime = super
|
21
|
+
searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime
|
22
|
+
self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render
|
23
|
+
runtime - searchkick_rt_after_render
|
24
|
+
end
|
25
|
+
|
26
|
+
def append_info_to_payload(payload)
|
27
|
+
super
|
28
|
+
payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def log_process_action(payload)
|
33
|
+
messages = super
|
34
|
+
runtime = payload[:searchkick_runtime]
|
35
|
+
messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
|
36
|
+
messages
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/searchkick/index.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
|
-
require "searchkick/index_options"
|
2
|
-
|
3
1
|
module Searchkick
|
4
2
|
class Index
|
5
|
-
include IndexOptions
|
6
|
-
|
7
3
|
attr_reader :name, :options
|
8
4
|
|
9
5
|
def initialize(name, options = {})
|
@@ -12,6 +8,10 @@ module Searchkick
|
|
12
8
|
@klass_document_type = {} # cache
|
13
9
|
end
|
14
10
|
|
11
|
+
def index_options
|
12
|
+
IndexOptions.new(self).index_options
|
13
|
+
end
|
14
|
+
|
15
15
|
def create(body = {})
|
16
16
|
client.indices.create index: name, body: body
|
17
17
|
end
|
@@ -38,12 +38,15 @@ module Searchkick
|
|
38
38
|
client.indices.exists_alias name: name
|
39
39
|
end
|
40
40
|
|
41
|
+
# call to_h for consistent results between elasticsearch gem 7 and 8
|
42
|
+
# could do for all API calls, but just do for ones where return value is focus for now
|
41
43
|
def mapping
|
42
|
-
client.indices.get_mapping
|
44
|
+
client.indices.get_mapping(index: name).to_h
|
43
45
|
end
|
44
46
|
|
47
|
+
# call to_h for consistent results between elasticsearch gem 7 and 8
|
45
48
|
def settings
|
46
|
-
client.indices.get_settings
|
49
|
+
client.indices.get_settings(index: name).to_h
|
47
50
|
end
|
48
51
|
|
49
52
|
def refresh_interval
|
@@ -64,16 +67,17 @@ module Searchkick
|
|
64
67
|
index: name,
|
65
68
|
body: {
|
66
69
|
query: {match_all: {}},
|
67
|
-
size: 0
|
70
|
+
size: 0,
|
71
|
+
track_total_hits: true
|
68
72
|
}
|
69
73
|
)
|
70
74
|
|
71
|
-
|
75
|
+
Results.new(nil, response).total_count
|
72
76
|
end
|
73
77
|
|
74
78
|
def promote(new_name, update_refresh_interval: false)
|
75
79
|
if update_refresh_interval
|
76
|
-
new_index =
|
80
|
+
new_index = Index.new(new_name, @options)
|
77
81
|
settings = options[:settings] || {}
|
78
82
|
refresh_interval = (settings[:index] && settings[:index][:refresh_interval]) || "1s"
|
79
83
|
new_index.update_settings(index: {refresh_interval: refresh_interval})
|
@@ -82,7 +86,8 @@ module Searchkick
|
|
82
86
|
old_indices =
|
83
87
|
begin
|
84
88
|
client.indices.get_alias(name: name).keys
|
85
|
-
rescue
|
89
|
+
rescue => e
|
90
|
+
raise e unless Searchkick.not_found_error?(e)
|
86
91
|
{}
|
87
92
|
end
|
88
93
|
actions = old_indices.map { |old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
|
@@ -94,7 +99,7 @@ module Searchkick
|
|
94
99
|
record_data = RecordData.new(self, record).record_data
|
95
100
|
|
96
101
|
# remove underscore
|
97
|
-
get_options =
|
102
|
+
get_options = record_data.to_h { |k, v| [k.to_s.sub(/\A_/, "").to_sym, v] }
|
98
103
|
|
99
104
|
client.get(get_options)["_source"]
|
100
105
|
end
|
@@ -103,11 +108,12 @@ module Searchkick
|
|
103
108
|
indices =
|
104
109
|
begin
|
105
110
|
if client.indices.respond_to?(:get_alias)
|
106
|
-
client.indices.get_alias
|
111
|
+
client.indices.get_alias(index: "#{name}*")
|
107
112
|
else
|
108
113
|
client.indices.get_aliases
|
109
114
|
end
|
110
|
-
rescue
|
115
|
+
rescue => e
|
116
|
+
raise e unless Searchkick.not_found_error?(e)
|
111
117
|
{}
|
112
118
|
end
|
113
119
|
indices = indices.select { |_k, v| v.empty? || v["aliases"].empty? } if unaliased
|
@@ -118,37 +124,52 @@ module Searchkick
|
|
118
124
|
def clean_indices
|
119
125
|
indices = all_indices(unaliased: true)
|
120
126
|
indices.each do |index|
|
121
|
-
|
127
|
+
Index.new(index).delete
|
122
128
|
end
|
123
129
|
indices
|
124
130
|
end
|
125
131
|
|
126
|
-
# record based
|
127
|
-
# use helpers for notifications
|
128
|
-
|
129
132
|
def store(record)
|
130
|
-
|
133
|
+
notify(record, "Store") do
|
134
|
+
queue_index([record])
|
135
|
+
end
|
131
136
|
end
|
132
137
|
|
133
138
|
def remove(record)
|
134
|
-
|
139
|
+
notify(record, "Remove") do
|
140
|
+
queue_delete([record])
|
141
|
+
end
|
135
142
|
end
|
136
143
|
|
137
144
|
def update_record(record, method_name)
|
138
|
-
|
145
|
+
notify(record, "Update") do
|
146
|
+
queue_update([record], method_name)
|
147
|
+
end
|
139
148
|
end
|
140
149
|
|
141
150
|
def bulk_delete(records)
|
142
|
-
|
151
|
+
return if records.empty?
|
152
|
+
|
153
|
+
notify_bulk(records, "Delete") do
|
154
|
+
queue_delete(records)
|
155
|
+
end
|
143
156
|
end
|
144
157
|
|
145
158
|
def bulk_index(records)
|
146
|
-
|
159
|
+
return if records.empty?
|
160
|
+
|
161
|
+
notify_bulk(records, "Import") do
|
162
|
+
queue_index(records)
|
163
|
+
end
|
147
164
|
end
|
148
165
|
alias_method :import, :bulk_index
|
149
166
|
|
150
167
|
def bulk_update(records, method_name)
|
151
|
-
|
168
|
+
return if records.empty?
|
169
|
+
|
170
|
+
notify_bulk(records, "Update") do
|
171
|
+
queue_update(records, method_name)
|
172
|
+
end
|
152
173
|
end
|
153
174
|
|
154
175
|
def search_id(record)
|
@@ -160,78 +181,96 @@ module Searchkick
|
|
160
181
|
end
|
161
182
|
|
162
183
|
def similar_record(record, **options)
|
163
|
-
like_text = retrieve(record).to_hash
|
164
|
-
.keep_if { |k, _| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
|
165
|
-
.values.compact.join(" ")
|
166
|
-
|
167
|
-
options[:where] ||= {}
|
168
|
-
options[:where][:_id] ||= {}
|
169
|
-
options[:where][:_id][:not] = Array(options[:where][:_id][:not]) + [record.id.to_s]
|
170
184
|
options[:per_page] ||= 10
|
171
|
-
options[:similar] =
|
185
|
+
options[:similar] = [RecordData.new(self, record).record_data]
|
186
|
+
options[:models] ||= [record.class] unless options.key?(:model)
|
172
187
|
|
173
|
-
|
174
|
-
Searchkick.search(like_text, model: record.class, **options)
|
188
|
+
Searchkick.search("*", **options)
|
175
189
|
end
|
176
190
|
|
177
191
|
def reload_synonyms
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
192
|
+
if Searchkick.opensearch?
|
193
|
+
client.transport.perform_request "POST", "_plugins/_refresh_search_analyzers/#{CGI.escape(name)}"
|
194
|
+
else
|
195
|
+
raise Error, "Requires Elasticsearch 7.3+" if Searchkick.server_below?("7.3.0")
|
196
|
+
begin
|
197
|
+
client.transport.perform_request("GET", "#{CGI.escape(name)}/_reload_search_analyzers")
|
198
|
+
rescue => e
|
199
|
+
raise Error, "Requires non-OSS version of Elasticsearch" if Searchkick.not_allowed_error?(e)
|
200
|
+
raise e
|
201
|
+
end
|
202
|
+
end
|
182
203
|
end
|
183
204
|
|
184
205
|
# queue
|
185
206
|
|
186
207
|
def reindex_queue
|
187
|
-
|
208
|
+
ReindexQueue.new(name)
|
188
209
|
end
|
189
210
|
|
190
211
|
# reindex
|
191
212
|
|
192
|
-
|
213
|
+
# note: this is designed to be used internally
|
214
|
+
# so it does not check object matches index class
|
215
|
+
def reindex(object, method_name: nil, full: false, **options)
|
216
|
+
if object.is_a?(Array)
|
217
|
+
# note: purposefully skip full
|
218
|
+
return reindex_records(object, method_name: method_name, **options)
|
219
|
+
end
|
220
|
+
|
221
|
+
if !object.respond_to?(:searchkick_klass)
|
222
|
+
raise Error, "Cannot reindex object"
|
223
|
+
end
|
224
|
+
|
225
|
+
scoped = Searchkick.relation?(object)
|
226
|
+
# call searchkick_klass for inheritance
|
227
|
+
relation = scoped ? object.all : Searchkick.scope(object.searchkick_klass).all
|
228
|
+
|
193
229
|
refresh = options.fetch(:refresh, !scoped)
|
194
230
|
options.delete(:refresh)
|
195
231
|
|
196
|
-
if method_name
|
197
|
-
|
198
|
-
|
232
|
+
if method_name || (scoped && !full)
|
233
|
+
mode = options.delete(:mode) || :inline
|
234
|
+
raise ArgumentError, "unsupported keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?
|
199
235
|
|
200
|
-
#
|
201
|
-
import_scope(relation, method_name: method_name,
|
202
|
-
self.refresh if refresh
|
203
|
-
true
|
204
|
-
elsif scoped && !full
|
205
|
-
# TODO throw ArgumentError
|
206
|
-
Searchkick.warn("unsupported keywords: #{options.keys.map(&:inspect).join(", ")}") if options.any?
|
207
|
-
|
208
|
-
# reindex association
|
209
|
-
import_scope(relation, scope: scope)
|
236
|
+
# import only
|
237
|
+
import_scope(relation, method_name: method_name, mode: mode)
|
210
238
|
self.refresh if refresh
|
211
239
|
true
|
212
240
|
else
|
213
|
-
|
214
|
-
|
241
|
+
async = options.delete(:async)
|
242
|
+
if async
|
243
|
+
if async.is_a?(Hash) && async[:wait]
|
244
|
+
# TODO warn in 5.1
|
245
|
+
# Searchkick.warn "async option is deprecated - use mode: :async, wait: true instead"
|
246
|
+
options[:wait] = true unless options.key?(:wait)
|
247
|
+
else
|
248
|
+
# TODO warn in 5.1
|
249
|
+
# Searchkick.warn "async option is deprecated - use mode: :async instead"
|
250
|
+
end
|
251
|
+
options[:mode] ||= :async
|
252
|
+
end
|
253
|
+
|
254
|
+
full_reindex(relation, **options)
|
215
255
|
end
|
216
256
|
end
|
217
257
|
|
218
258
|
def create_index(index_options: nil)
|
219
259
|
index_options ||= self.index_options
|
220
|
-
index =
|
260
|
+
index = Index.new("#{name}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}", @options)
|
221
261
|
index.create(index_options)
|
222
262
|
index
|
223
263
|
end
|
224
264
|
|
225
265
|
def import_scope(relation, **options)
|
226
|
-
|
266
|
+
relation_indexer.reindex(relation, **options)
|
227
267
|
end
|
228
268
|
|
229
269
|
def batches_left
|
230
|
-
|
270
|
+
relation_indexer.batches_left
|
231
271
|
end
|
232
272
|
|
233
|
-
#
|
234
|
-
|
273
|
+
# private
|
235
274
|
def klass_document_type(klass, ignore_type = false)
|
236
275
|
@klass_document_type[[klass, ignore_type]] ||= begin
|
237
276
|
if !ignore_type && klass.searchkick_klass.searchkick_options[:_type]
|
@@ -244,7 +283,7 @@ module Searchkick
|
|
244
283
|
end
|
245
284
|
end
|
246
285
|
|
247
|
-
#
|
286
|
+
# private
|
248
287
|
def conversions_fields
|
249
288
|
@conversions_fields ||= begin
|
250
289
|
conversions = Array(options[:conversions])
|
@@ -252,10 +291,12 @@ module Searchkick
|
|
252
291
|
end
|
253
292
|
end
|
254
293
|
|
294
|
+
# private
|
255
295
|
def suggest_fields
|
256
296
|
@suggest_fields ||= Array(options[:suggest]).map(&:to_s)
|
257
297
|
end
|
258
298
|
|
299
|
+
# private
|
259
300
|
def locations_fields
|
260
301
|
@locations_fields ||= begin
|
261
302
|
locations = Array(options[:locations])
|
@@ -274,8 +315,20 @@ module Searchkick
|
|
274
315
|
Searchkick.client
|
275
316
|
end
|
276
317
|
|
277
|
-
def
|
278
|
-
|
318
|
+
def queue_index(records)
|
319
|
+
Searchkick.indexer.queue(records.map { |r| RecordData.new(self, r).index_data })
|
320
|
+
end
|
321
|
+
|
322
|
+
def queue_delete(records)
|
323
|
+
Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| RecordData.new(self, r).delete_data })
|
324
|
+
end
|
325
|
+
|
326
|
+
def queue_update(records, method_name)
|
327
|
+
Searchkick.indexer.queue(records.map { |r| RecordData.new(self, r).update_data(method_name) })
|
328
|
+
end
|
329
|
+
|
330
|
+
def relation_indexer
|
331
|
+
@relation_indexer ||= RelationIndexer.new(self)
|
279
332
|
end
|
280
333
|
|
281
334
|
def index_settings
|
@@ -286,13 +339,24 @@ module Searchkick
|
|
286
339
|
index.import_scope(relation, **import_options)
|
287
340
|
end
|
288
341
|
|
342
|
+
def reindex_records(object, mode: nil, refresh: false, **options)
|
343
|
+
mode ||= Searchkick.callbacks_value || @options[:callbacks] || :inline
|
344
|
+
mode = :inline if mode == :bulk
|
345
|
+
|
346
|
+
result = RecordIndexer.new(self).reindex(object, mode: mode, full: false, **options)
|
347
|
+
self.refresh if refresh
|
348
|
+
result
|
349
|
+
end
|
350
|
+
|
289
351
|
# https://gist.github.com/jarosan/3124884
|
290
352
|
# http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/
|
291
|
-
def
|
353
|
+
def full_reindex(relation, import: true, resume: false, retain: false, mode: nil, refresh_interval: nil, scope: nil, wait: nil)
|
354
|
+
raise ArgumentError, "wait only available in :async mode" if !wait.nil? && mode != :async
|
355
|
+
|
292
356
|
if resume
|
293
357
|
index_name = all_indices.sort.last
|
294
|
-
raise
|
295
|
-
index =
|
358
|
+
raise Error, "No index to resume" unless index_name
|
359
|
+
index = Index.new(index_name, @options)
|
296
360
|
else
|
297
361
|
clean_indices unless retain
|
298
362
|
|
@@ -302,9 +366,9 @@ module Searchkick
|
|
302
366
|
end
|
303
367
|
|
304
368
|
import_options = {
|
305
|
-
|
306
|
-
async: async,
|
369
|
+
mode: (mode || :inline),
|
307
370
|
full: true,
|
371
|
+
resume: resume,
|
308
372
|
scope: scope
|
309
373
|
}
|
310
374
|
|
@@ -316,7 +380,7 @@ module Searchkick
|
|
316
380
|
import_before_promotion(index, relation, **import_options) if import
|
317
381
|
|
318
382
|
# get existing indices to remove
|
319
|
-
unless async
|
383
|
+
unless mode == :async
|
320
384
|
check_uuid(uuid, index.uuid)
|
321
385
|
promote(index.name, update_refresh_interval: !refresh_interval.nil?)
|
322
386
|
clean_indices unless retain
|
@@ -329,8 +393,8 @@ module Searchkick
|
|
329
393
|
index.import_scope(relation, **import_options) if import
|
330
394
|
end
|
331
395
|
|
332
|
-
if async
|
333
|
-
if
|
396
|
+
if mode == :async
|
397
|
+
if wait
|
334
398
|
puts "Created index: #{index.name}"
|
335
399
|
puts "Jobs queued. Waiting..."
|
336
400
|
loop do
|
@@ -354,9 +418,9 @@ module Searchkick
|
|
354
418
|
index.refresh
|
355
419
|
true
|
356
420
|
end
|
357
|
-
rescue
|
358
|
-
if e.message.include?("No handler for type [text]")
|
359
|
-
raise UnsupportedVersionError
|
421
|
+
rescue => e
|
422
|
+
if Searchkick.transport_error?(e) && (e.message.include?("No handler for type [text]") || e.message.include?("class java.util.ArrayList cannot be cast to class java.util.Map"))
|
423
|
+
raise UnsupportedVersionError
|
360
424
|
end
|
361
425
|
|
362
426
|
raise e
|
@@ -368,7 +432,36 @@ module Searchkick
|
|
368
432
|
# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-creation
|
369
433
|
def check_uuid(old_uuid, new_uuid)
|
370
434
|
if old_uuid != new_uuid
|
371
|
-
raise
|
435
|
+
raise Error, "Safety check failed - only run one Model.reindex per model at a time"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
def notify(record, name)
|
440
|
+
if Searchkick.callbacks_value == :bulk
|
441
|
+
yield
|
442
|
+
else
|
443
|
+
name = "#{record.class.searchkick_klass.name} #{name}" if record && record.class.searchkick_klass
|
444
|
+
event = {
|
445
|
+
name: name,
|
446
|
+
id: search_id(record)
|
447
|
+
}
|
448
|
+
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
449
|
+
yield
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def notify_bulk(records, name)
|
455
|
+
if Searchkick.callbacks_value == :bulk
|
456
|
+
yield
|
457
|
+
else
|
458
|
+
event = {
|
459
|
+
name: "#{records.first.class.searchkick_klass.name} #{name}",
|
460
|
+
count: records.size
|
461
|
+
}
|
462
|
+
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
463
|
+
yield
|
464
|
+
end
|
372
465
|
end
|
373
466
|
end
|
374
467
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Searchkick
|
2
|
+
class IndexCache
|
3
|
+
def initialize(max_size: 20)
|
4
|
+
@data = {}
|
5
|
+
@mutex = Mutex.new
|
6
|
+
@max_size = max_size
|
7
|
+
end
|
8
|
+
|
9
|
+
# probably a better pattern for this
|
10
|
+
# but keep it simple
|
11
|
+
def fetch(name)
|
12
|
+
# thread-safe in MRI without mutex
|
13
|
+
# due to how context switching works
|
14
|
+
@mutex.synchronize do
|
15
|
+
if @data.key?(name)
|
16
|
+
@data[name]
|
17
|
+
else
|
18
|
+
@data.clear if @data.size >= @max_size
|
19
|
+
@data[name] = yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@mutex.synchronize do
|
26
|
+
@data.clear
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|