mongodb_meilisearch 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- def search_index
74
- Search::Client.instance.index(search_index_name)
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 = search_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
- search_index.update_documents(updated_documents, primary_search_key)
260
+ administratable_index.update_documents(updated_documents, primary_search_key)
255
261
  else
256
- search_index.update_documents!(updated_documents, primary_search_key)
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
- search_index.add_documents(new_documents, primary_search_key)
272
+ administratable_index.add_documents(new_documents, primary_search_key)
267
273
  else
268
- search_index.add_documents!(new_documents, primary_search_key)
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
- search_index.stats
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
- search_index.number_of_documents
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
- search_index.delete_index
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
- search_index.delete_all_documents
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
- search_index.delete_all_documents!
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 ||= search_index.get_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 ||= search_index.get_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
- search_index.update_filterable_attributes(new_attributes)
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
- search_index.wait_for_task(task["taskUid"])
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
- search_index.update_sortable_attributes(new_attributes)
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
- search_index.wait_for_task(task["taskUid"])
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 :client
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
- url = ENV.fetch("MEILISEARCH_URL")
11
- # MEILISEARCH_API_KEY is for mongodb_meilisearch v1.2.1 & earlier
12
- api_key = ENV.fetch("MEILI_MASTER_KEY") || ENV.fetch("MEILISEARCH_API_KEY")
13
- timeout = ENV.fetch("MEILISEARCH_TIMEOUT", 10).to_i
14
- max_retries = ENV.fetch("MEILISEARCH_MAX_RETRIES", 2).to_i
15
- if url.present? && api_key.present?
16
- @client = MeiliSearch::Client.new(url, api_key,
17
- timeout: timeout,
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
- !@client.nil?
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 method_missing(m, *args, &block)
31
- if @client.respond_to? m.to_sym
32
- @client.send(m, *args, &block)
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
- raise ArgumentError.new("Method `#{m}` doesn't exist in #{@client.inspect}.")
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 respond_to_missing?(method_name, include_private = false)
39
- @client.respond_to?(method_name.to_sym) || super
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
@@ -0,0 +1,7 @@
1
+ # lib/errors.rb
2
+
3
+ module Search
4
+ module Errors
5
+ class ConfigurationError < StandardError; end
6
+ end
7
+ 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
- search_index.add_documents(
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 = search_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
- search_index.update_documents(
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
- search_index.update_documents!(
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
- search_index.delete_document(send(primary_search_key).to_s)
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
- search_index.delete_document!(send(primary_search_key).to_s)
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 search index
90
+ # A convenience method to ease accessing the administratable index
90
91
  # from the ClassMethods
91
- def search_index
92
- self.class.search_index
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: 1.3.0
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-12 00:00:00.000000000 Z
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.md
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