mongodb_meilisearch 1.3.0 → 2.0.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/Gemfile.lock +1 -1
- data/README.org +621 -0
- data/lib/mongodb_meilisearch/version.rb +1 -1
- data/lib/search/class_methods.rb +25 -19
- data/lib/search/client.rb +148 -20
- data/lib/search/errors.rb +7 -0
- data/lib/search/instance_methods.rb +11 -10
- metadata +4 -3
- data/README.md +0 -531
data/lib/search/class_methods.rb
CHANGED
@@ -69,9 +69,15 @@ module Search
|
|
69
69
|
class_index_name
|
70
70
|
end
|
71
71
|
|
72
|
+
# @return [MeiliSearch::Index] the search-only search index for this class
|
73
|
+
def searchable_index
|
74
|
+
Search::Client.instance.search_client.index(search_index_name)
|
75
|
+
end
|
76
|
+
|
72
77
|
# @return [MeiliSearch::Index] the search index for this class
|
73
|
-
|
74
|
-
|
78
|
+
# in a form that can modify the index
|
79
|
+
def administratable_index
|
80
|
+
Search::Client.instance.admin_client.index(search_index_name)
|
75
81
|
end
|
76
82
|
|
77
83
|
# MeiliSearch allows you to define the ranking of search results. Alas, this is not based
|
@@ -96,7 +102,7 @@ module Search
|
|
96
102
|
# @return [Hash] raw results directly from meilisearch-ruby gem
|
97
103
|
# This is a hash with paging information and more.
|
98
104
|
def raw_search(search_string, options = search_options)
|
99
|
-
index =
|
105
|
+
index = searchable_index
|
100
106
|
index.search(search_string, options)
|
101
107
|
end
|
102
108
|
|
@@ -251,9 +257,9 @@ module Search
|
|
251
257
|
# search_indexable_hash
|
252
258
|
def update_documents(updated_documents, async: true)
|
253
259
|
if async
|
254
|
-
|
260
|
+
administratable_index.update_documents(updated_documents, primary_search_key)
|
255
261
|
else
|
256
|
-
|
262
|
+
administratable_index.update_documents!(updated_documents, primary_search_key)
|
257
263
|
end
|
258
264
|
end
|
259
265
|
|
@@ -263,9 +269,9 @@ module Search
|
|
263
269
|
def add_documents(new_documents, async: true)
|
264
270
|
configure_attributes_and_index_if_needed!
|
265
271
|
if async
|
266
|
-
|
272
|
+
administratable_index.add_documents(new_documents, primary_search_key)
|
267
273
|
else
|
268
|
-
|
274
|
+
administratable_index.add_documents!(new_documents, primary_search_key)
|
269
275
|
end
|
270
276
|
end
|
271
277
|
|
@@ -286,14 +292,14 @@ module Search
|
|
286
292
|
# A convenience method that wraps MeiliSearch::Index#stats
|
287
293
|
# See https://www.meilisearch.com/docs/reference/api/stats for more info
|
288
294
|
def search_stats
|
289
|
-
|
295
|
+
administratable_index.stats
|
290
296
|
end
|
291
297
|
|
292
298
|
# @return [Integer] the number of documents in the search index
|
293
299
|
# as reported via stats.
|
294
300
|
# See https://www.meilisearch.com/docs/reference/api/stats for more info
|
295
301
|
def searchable_documents
|
296
|
-
|
302
|
+
administratable_index.number_of_documents
|
297
303
|
end
|
298
304
|
|
299
305
|
# @return [Boolean] indicating if search ids should be prefixed with the class name
|
@@ -309,19 +315,19 @@ module Search
|
|
309
315
|
# you should use this, you're probably mistaken.
|
310
316
|
# @warning this will delete the index and all documents in it
|
311
317
|
def delete_index!
|
312
|
-
|
318
|
+
administratable_index.delete_index
|
313
319
|
end
|
314
320
|
|
315
321
|
# Asynchronously deletes all documents from the search index
|
316
322
|
# regardless of what model they're associated with.
|
317
323
|
def delete_all_documents
|
318
|
-
|
324
|
+
administratable_index.delete_all_documents
|
319
325
|
end
|
320
326
|
|
321
327
|
# Synchronously deletes all documents from the search index
|
322
328
|
# regardless of what model they're associated with.
|
323
329
|
def delete_all_documents!
|
324
|
-
|
330
|
+
administratable_index.delete_all_documents!
|
325
331
|
end
|
326
332
|
|
327
333
|
# Asynchronously delete & reindex all instances of this class.
|
@@ -395,11 +401,11 @@ module Search
|
|
395
401
|
# @return [Array] - an array of attributes configured as sortable
|
396
402
|
# in the index.
|
397
403
|
def meilisearch_sortable_attributes
|
398
|
-
@_meilisearch_sortable_attributes ||=
|
404
|
+
@_meilisearch_sortable_attributes ||= administratable_index.get_sortable_attributes
|
399
405
|
end
|
400
406
|
|
401
407
|
def meilisearch_filterable_attributes
|
402
|
-
@_meilisearch_filterable_attributes ||=
|
408
|
+
@_meilisearch_filterable_attributes ||= administratable_index.get_filterable_attributes
|
403
409
|
end
|
404
410
|
|
405
411
|
def reset_cached_data!
|
@@ -421,7 +427,7 @@ module Search
|
|
421
427
|
# this is expected to happen the first time an instance
|
422
428
|
# of a new model is saved.
|
423
429
|
raise unless e.message.match?(/Index `\S+` not found\./)
|
424
|
-
Search::Client.instance.create_index(search_index_name)
|
430
|
+
Search::Client.instance.admin_client.create_index(search_index_name)
|
425
431
|
end
|
426
432
|
|
427
433
|
return if indexes_filterable_attributes.include?("object_class")
|
@@ -434,13 +440,13 @@ module Search
|
|
434
440
|
# which may take time. Best to run this in a background job
|
435
441
|
# for large datasets.
|
436
442
|
def set_filterable_attributes(new_attributes = filterable_attributes)
|
437
|
-
|
443
|
+
administratable_index.update_filterable_attributes(new_attributes)
|
438
444
|
end
|
439
445
|
|
440
446
|
def set_filterable_attributes!(new_attributes = filterable_attributes)
|
441
447
|
# meilisearch-ruby doesn't provide a synchronous version of this
|
442
448
|
task = set_filterable_attributes(new_attributes)
|
443
|
-
|
449
|
+
administratable_index.wait_for_task(task["taskUid"])
|
444
450
|
end
|
445
451
|
|
446
452
|
# Updates the sortable attributes in the search index.
|
@@ -448,13 +454,13 @@ module Search
|
|
448
454
|
# which may take time. Best to run this in a background job
|
449
455
|
# for large datasets.
|
450
456
|
def set_sortable_attributes(new_attributes = sortable_attributes)
|
451
|
-
|
457
|
+
administratable_index.update_sortable_attributes(new_attributes)
|
452
458
|
end
|
453
459
|
|
454
460
|
def set_sortable_attributes!(new_attributes = sortable_attributes)
|
455
461
|
# meilisearch-ruby doesn't provide a synchronous version of this
|
456
462
|
task = set_sortable_attributes(new_attributes)
|
457
|
-
|
463
|
+
administratable_index.wait_for_task(task["taskUid"])
|
458
464
|
end
|
459
465
|
|
460
466
|
private
|
data/lib/search/client.rb
CHANGED
@@ -1,42 +1,170 @@
|
|
1
1
|
require "singleton"
|
2
|
+
require "logger"
|
2
3
|
|
3
4
|
module Search
|
4
5
|
class Client
|
5
6
|
include Singleton
|
6
|
-
attr_reader :
|
7
|
+
attr_reader :admin_client, :search_client
|
8
|
+
attr_accessor :logger
|
7
9
|
|
8
10
|
def initialize
|
11
|
+
@logger = default_logger
|
9
12
|
if ENV.fetch("SEARCH_ENABLED", "true") == "true"
|
10
|
-
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
max_retries: max_retries)
|
19
|
-
else
|
20
|
-
Rails.logger.warn("UNABLE TO CONFIGURE SEARCH. Check env vars.")
|
21
|
-
@client = nil
|
13
|
+
initialize_clients
|
14
|
+
# WARNING: ⚠ clients MAY be nil depending on what api keys were provided
|
15
|
+
# In this case rails logger warnings and/or errors will have already
|
16
|
+
# been created.
|
17
|
+
unless admin_client || search_client
|
18
|
+
raise Search::Errors::ConfigurationError.new(
|
19
|
+
"Unable to configure any MeliSearch clients. Check env vars."
|
20
|
+
)
|
22
21
|
end
|
22
|
+
else
|
23
|
+
@logger.info("SEARCH_ENABLED is not \"true\" - mongodb_meilisearch NOT initialized")
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
27
|
+
# Indicates if there is a client available to
|
28
|
+
# administration OR searches.
|
26
29
|
def enabled?
|
27
|
-
!@
|
30
|
+
!@admin_client.nil? || !@search_client.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Indicates if there is a client available
|
34
|
+
# that has been configured with an admin key.
|
35
|
+
def admin_enabled?
|
36
|
+
!@admin_client.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
# @deprecated use search_client for searches
|
40
|
+
# & admin_client for everything else
|
41
|
+
def client
|
42
|
+
@logger.info("Search::Client.instance.client is a deprecated method")
|
43
|
+
@admin_client || @search_client
|
28
44
|
end
|
29
45
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
46
|
+
def initialize_clients
|
47
|
+
# see what env vars they've configured
|
48
|
+
search_api_key = ENV.fetch("MEILISEARCH_SEARCH_KEY", nil)
|
49
|
+
admin_api_key = ENV.fetch("MEILISEARCH_ADMIN_KEY", nil)
|
50
|
+
search_api_key = nil if search_api_key == ""
|
51
|
+
admin_api_key = nil if admin_api_key == ""
|
52
|
+
|
53
|
+
# if there is a master key (and it's valid) we're guaranteed to have
|
54
|
+
# default api keys we can use for search & admin
|
55
|
+
if search_api_key.nil? || admin_api_key.nil?
|
56
|
+
m_c = master_client
|
57
|
+
if m_c
|
58
|
+
default_keys = get_default_keys(m_c)
|
59
|
+
search_api_key ||= default_keys[:search]
|
60
|
+
admin_api_key ||= default_keys[:admin]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if !admin_api_key.nil?
|
65
|
+
@admin_client = initialize_new_client(
|
66
|
+
url: url,
|
67
|
+
api_key: admin_api_key,
|
68
|
+
timeout: timeout,
|
69
|
+
max_retries: max_retries
|
70
|
+
)
|
71
|
+
@logger.debug("initialized admin client with admin key: #{admin_api_key[0..5]}…")
|
33
72
|
else
|
34
|
-
|
73
|
+
@logger.error("UNABLE TO CONFIGURE MEILISEARCH ADMINISTRATION CLIENT. Check env vars.")
|
74
|
+
@admin_client = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
if !search_api_key.nil?
|
78
|
+
@search_client = initialize_new_client(
|
79
|
+
url: url,
|
80
|
+
api_key: search_api_key,
|
81
|
+
timeout: timeout,
|
82
|
+
max_retries: max_retries
|
83
|
+
)
|
84
|
+
@logger.debug("initialized search client with search key: #{search_api_key[0..5]}…")
|
85
|
+
else
|
86
|
+
@logger.error("UNABLE TO CONFIGURE GENERAL MEILISEARCH SEARCH CLIENT. Check env vars.")
|
87
|
+
@search_client = nil
|
88
|
+
end
|
89
|
+
rescue MeiliSearch::ApiError => e
|
90
|
+
@logger.error("MeiliSearch Api Error when attempting to list keys: #{e}")
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize_new_client(api_key:, url:, timeout:, max_retries:)
|
94
|
+
MeiliSearch::Client.new(url, api_key,
|
95
|
+
timeout: timeout,
|
96
|
+
max_retries: max_retries)
|
97
|
+
end
|
98
|
+
|
99
|
+
def master_client(master_api_key = nil)
|
100
|
+
master_api_key = nil if master_api_key == ""
|
101
|
+
master_api_key ||= ENV.fetch("MEILI_MASTER_KEY", nil)
|
102
|
+
if !url || !master_api_key
|
103
|
+
|
104
|
+
unless master_api_key
|
105
|
+
@logger.error(
|
106
|
+
"MEILI_MASTER_KEY is not set. Cannot create master client."
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
|
113
|
+
initialize_new_client(
|
114
|
+
url: url,
|
115
|
+
api_key: ENV.fetch("MEILI_MASTER_KEY"),
|
116
|
+
timeout: timeout,
|
117
|
+
max_retries: max_retries
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def url
|
122
|
+
maybe_url = ENV.fetch("MEILISEARCH_URL", nil)
|
123
|
+
unless maybe_url
|
124
|
+
@logger.error(
|
125
|
+
"MEILI_MASTER_KEY is not set. Cannot create master client."
|
126
|
+
)
|
127
|
+
end
|
128
|
+
maybe_url
|
129
|
+
end
|
130
|
+
|
131
|
+
def timeout
|
132
|
+
ENV.fetch("MEILISEARCH_TIMEOUT", 10).to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
def max_retries
|
136
|
+
ENV.fetch("MEILISEARCH_MAX_RETRIES", 2).to_i
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_default_keys(m_c)
|
140
|
+
# NOTE: master_client /can/ return nil
|
141
|
+
if m_c.nil?
|
142
|
+
m_c = master_client
|
143
|
+
elsif m_c.is_a?(String)
|
144
|
+
m_c = master_client(m_c)
|
145
|
+
end
|
146
|
+
|
147
|
+
unless m_c
|
148
|
+
raise Search::Errors::ConfigurationError.new(
|
149
|
+
"Can't retrieve default keys without Master API Key & URL configured."
|
150
|
+
)
|
151
|
+
end
|
152
|
+
keys = m_c.keys
|
153
|
+
response = {search: nil, admin: nil}
|
154
|
+
|
155
|
+
keys&.[]("results")&.each do |hash|
|
156
|
+
if hash["name"] == "Default Search API Key"
|
157
|
+
response[:search] = hash["key"]
|
158
|
+
elsif hash["name"] == "Default Admin API Key"
|
159
|
+
response[:admin] = hash["key"]
|
160
|
+
end
|
35
161
|
end
|
162
|
+
response
|
36
163
|
end
|
37
164
|
|
38
|
-
def
|
39
|
-
|
165
|
+
def default_logger
|
166
|
+
in_rails = Module.constants.include?(:Rails)
|
167
|
+
in_rails ? Rails.logger : Logger.new($stdout)
|
40
168
|
end
|
41
169
|
end
|
42
170
|
end
|
@@ -3,7 +3,7 @@ module Search
|
|
3
3
|
# Adds this record to the search index asynchronously
|
4
4
|
def add_to_search
|
5
5
|
self.class.configure_attributes_and_index_if_needed!
|
6
|
-
|
6
|
+
administratable_index.add_documents(
|
7
7
|
[search_indexable_hash],
|
8
8
|
primary_search_key.to_s
|
9
9
|
)
|
@@ -12,7 +12,7 @@ module Search
|
|
12
12
|
# Adds this record to the search index synchronously
|
13
13
|
def add_to_search!
|
14
14
|
self.class.configure_attributes_and_index_if_needed!
|
15
|
-
index =
|
15
|
+
index = administratable_index
|
16
16
|
documents = [search_indexable_hash]
|
17
17
|
pk = primary_search_key.to_s
|
18
18
|
index.add_documents!(documents, pk)
|
@@ -20,7 +20,7 @@ module Search
|
|
20
20
|
|
21
21
|
# Updates this record in the search index asynchronously
|
22
22
|
def update_in_search
|
23
|
-
|
23
|
+
administratable_index.update_documents(
|
24
24
|
[search_indexable_hash],
|
25
25
|
primary_search_key
|
26
26
|
)
|
@@ -28,7 +28,7 @@ module Search
|
|
28
28
|
|
29
29
|
# Updates this record in the search index synchronously
|
30
30
|
def update_in_search!
|
31
|
-
|
31
|
+
administratable_index.update_documents!(
|
32
32
|
[search_indexable_hash],
|
33
33
|
primary_search_key
|
34
34
|
)
|
@@ -36,12 +36,12 @@ module Search
|
|
36
36
|
|
37
37
|
# Removes this record from the search asynchronously
|
38
38
|
def remove_from_search
|
39
|
-
|
39
|
+
administratable_index.delete_document(send(primary_search_key).to_s)
|
40
40
|
end
|
41
41
|
|
42
42
|
# Removes this record from the search synchronously
|
43
43
|
def remove_from_search!
|
44
|
-
|
44
|
+
administratable_index.delete_document!(send(primary_search_key).to_s)
|
45
45
|
end
|
46
46
|
|
47
47
|
def searchable_attributes
|
@@ -60,6 +60,7 @@ module Search
|
|
60
60
|
# _unless_ one is already defined. This gem relies on "object_class" being present
|
61
61
|
# in returned results
|
62
62
|
def search_indexable_hash
|
63
|
+
return @_search_indexable_hash if defined?(@_search_indexable_hash)
|
63
64
|
klass = self.class
|
64
65
|
# the to_s & to_sym is just safety in case someone
|
65
66
|
# defined searchable_attributes as an array of strings
|
@@ -83,13 +84,13 @@ module Search
|
|
83
84
|
|
84
85
|
hash["object_class"] = klass.name unless hash.has_key?("object_class")
|
85
86
|
hash["original_document_id"] = _id.to_s if klass.has_class_prefixed_search_ids?
|
86
|
-
hash
|
87
|
+
@_search_indexable_hash = hash
|
87
88
|
end
|
88
89
|
|
89
|
-
# A convenience method to ease accessing the
|
90
|
+
# A convenience method to ease accessing the administratable index
|
90
91
|
# from the ClassMethods
|
91
|
-
def
|
92
|
-
self.class.
|
92
|
+
def administratable_index
|
93
|
+
self.class.administratable_index
|
93
94
|
end
|
94
95
|
|
95
96
|
# A convenience method to ease accessing the primary search key
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongodb_meilisearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- masukomi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -156,13 +156,14 @@ files:
|
|
156
156
|
- Gemfile
|
157
157
|
- Gemfile.lock
|
158
158
|
- LICENSE.txt
|
159
|
-
- README.
|
159
|
+
- README.org
|
160
160
|
- Rakefile
|
161
161
|
- lefthook.yml
|
162
162
|
- lib/mongodb_meilisearch.rb
|
163
163
|
- lib/mongodb_meilisearch/version.rb
|
164
164
|
- lib/search/class_methods.rb
|
165
165
|
- lib/search/client.rb
|
166
|
+
- lib/search/errors.rb
|
166
167
|
- lib/search/instance_methods.rb
|
167
168
|
- sig/mongodb_meilisearch.rbs
|
168
169
|
homepage: https://github.com/masukomi/mongodb_meilisearch
|