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.
@@ -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