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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff219e5de4c77b4b06515946701f57eee82588691ec7931d963a70011409c79a
4
- data.tar.gz: c51b7f234431417abe62fdd5365a106b4d493fe9e49b7802726cdd61a519db48
3
+ metadata.gz: 0c07c25d10efdfee292401ba54dacb34aee8abd153226a3618a2c88de96661dc
4
+ data.tar.gz: 40ec770c86ab522f3cf49ff950230a8f82f6527d4a3b33842f8d56c12f47067a
5
5
  SHA512:
6
- metadata.gz: b21b329983c8ac89de3e8543cccb021e63a7d1411b0cb464885ea3019fe815f7f72de8f408da1bc18d38cc2ebeac929f5491ec643ee3ea5e8a102dde3c582aae
7
- data.tar.gz: 4c44d1ae3784917566bd6a1a2bb3993a718fe57d5cf97e7e9af21587a3587fda1d1c77764606ad21dd06b9c8e107eff82e7173d8d019ecd2e18dad46094a19a9
6
+ metadata.gz: db89325982a30521bec7fcaee27f0da61a177b7738591dbf0b95c36666f3a02e94a85e3403a7c2b81a92d0396839b1344dc7c63a4a4ae937793c743bcbb26e4f
7
+ data.tar.gz: 05415b71991b53ff28ab4c10e5c3b9ea27ef7399f1c91d1e0444974469b9649d24191ec0438a91045438c95207fa4dcf6f4c4ea545008e5d30b52574daf5752b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mongodb_meilisearch (1.3.0)
4
+ mongodb_meilisearch (2.0.0)
5
5
  meilisearch
6
6
  mongoid (~> 7.0)
7
7
  rails
data/README.org ADDED
@@ -0,0 +1,621 @@
1
+ #+OPTIONS: toc:2
2
+ * Table of Contents :TOC:
3
+ - [[#mongodbmeilisearch][MongodbMeilisearch]]
4
+ - [[#installation][Installation]]
5
+ - [[#usage][Usage]]
6
+ - [[#model-integration][Model Integration]]
7
+ - [[#indexes][Indexes]]
8
+ - [[#searching][Searching]]
9
+ - [[#upgrading][Upgrading]]
10
+ - [[#development][Development]]
11
+ - [[#license][License]]
12
+ - [[#code-of-conduct][Code of Conduct]]
13
+ - [[#footnotes][Footnotes]]
14
+
15
+ * MongodbMeilisearch
16
+ A simple gem for integrating
17
+ [[https://www.meilisearch.com][Meilisearch]] into Ruby applications
18
+ that are backed by [[https://www.mongodb.com/][MongoDB]].
19
+
20
+ ** Installation
21
+ Install the gem and add to the application's Gemfile by executing:
22
+
23
+ #+begin_example
24
+ $ bundle add mongodb_meilisearch
25
+ #+end_example
26
+
27
+ If bundler is not being used to manage dependencies, install the gem by
28
+ executing:
29
+
30
+ #+begin_example
31
+ $ gem install mongodb_meilisearch
32
+ #+end_example
33
+
34
+ ** Usage
35
+ A high level overview
36
+
37
+ *** Pre-Requisites
38
+ - [[https://www.meilisearch.com][Meilisearch]]
39
+ - [[https://www.mongodb.com/][MongoDB]]
40
+ - Some models that =include Mongoid::Document=
41
+
42
+ *** Configuration
43
+ **** Background
44
+ Meilisearch uses 3 different API keys.
45
+ - A Master API Key
46
+ - An Administrative API key
47
+ - A Search API key
48
+
49
+ The Master API key should be used when starting Meilisearch,[fn:macos] but should /never/ be used when searching or administering [[https://www.meilisearch.com/docs/learn/security/basic_security][for security reasons]].
50
+
51
+ When you start it with a master key, it will create default Search & Administrative API Keys.[fn:default_keys] These ~mongodb_meilisearch~ uses these for searching & administering respectively.
52
+
53
+ **** Details
54
+ =MEILI_MASTER_KEY= is the environment variable Meilisearch uses for setting its master key at startup if it isn't provided via command line arguments.
55
+
56
+ =MEILISEARCH_SEARCH_KEY= & =MEILISEARCH_ADMIN_KEY= are for Search & administration respectively. Administration is /everything/ that isn't search, including things like adding new records to the index.
57
+
58
+ I recommend you define the Search & Administration keys. If you provide both of those the =MEILI_MASTER_KEY= will /not/ be required, /or used/ if present. If you only know the master key, there are details below for how to obtain the others.
59
+
60
+ Define the following variables in your environment (or =.env= file if
61
+ you're using [[https://github.com/bkeepers/dotenv][dotenv]]). The url below is the default one Meilisearch
62
+ uses when run locally. Change it if you're running it on a different box or have otherwise changed the default location setup.
63
+
64
+ #+begin_src sh
65
+ SEARCH_ENABLED=true
66
+ MEILI_MASTER_KEY=<your search master key>
67
+ MEILISEARCH_SEARCH_KEY=<your search api key here>
68
+ MEILISEARCH_ADMIN_KEY=<your admin api key here>
69
+ MEILISEARCH_URL=http://127.0.0.1:7700
70
+ #+end_src
71
+
72
+ Optional configuration
73
+
74
+ #+begin_src sh
75
+ MEILISEARCH_TIMEOUT=10
76
+ MEILISEARCH_MAX_RETRIES=2
77
+ #+end_src
78
+
79
+
80
+ 👋 Environment variables starting with =MEILI_= are [[https://www.meilisearch.com/docs/learn/configuration/instance_options][ones defined by Meilisearch]]. The ones starting with =MEILISEARCH_= are specific to this library.
81
+
82
+ ***** Finding your Search & Admin keys
83
+ Meilisearch doesn't make it easy to find out what your default search & administration API keys are, and I haven't found a way to define them yourself.
84
+
85
+ Fortunately for you, this library has an easy way, and you've got this library. 😉
86
+
87
+ #+begin_src ruby
88
+ Search::Client.instance.get_default_keys("your master key here")
89
+ #+end_src
90
+
91
+ This will return a hash with the keys you'll need.
92
+
93
+ #+begin_src ruby
94
+ {:search=>"your search api key",
95
+ :admin=>"your admin api key"}
96
+ #+end_src
97
+
98
+ Use those keys in your environment. The next time the =Search::Client= is initialized it'll use them.
99
+
100
+ ** Model Integration
101
+ Add the following near the top of your model. Only the =extend= and
102
+ =include= lines are required. This assumes your model also includes
103
+ =Mongoid::Document=
104
+
105
+ #+begin_src ruby
106
+ include Search::InstanceMethods
107
+ extend Search::ClassMethods
108
+ #+end_src
109
+
110
+ If you want Rails to automatically add, update, and delete records from
111
+ the index, add the following to your model.
112
+
113
+ You can override these methods if needed, but you're unlikely to want
114
+ to.
115
+
116
+ #+begin_src ruby
117
+ # enabled?() is controlled by the SEARCH_ENABLED environment variable
118
+ if Search::Client.instance.enabled?
119
+ after_create :add_to_search
120
+ after_update :update_in_search
121
+ after_destroy :remove_from_search
122
+ end
123
+ #+end_src
124
+
125
+ Assuming you've done the above a new index will be created with a name
126
+ that corresponds to your model's name, only in snake case. All of your
127
+ models fields will be indexed and
128
+ [[https://www.meilisearch.com/docs/learn/fine_tuning_results/filtering][filterable]].
129
+
130
+ *** Example Rails Model
131
+ Here's what it looks like when you put it all together in a Rails model
132
+ with the default behavior.
133
+
134
+ #+begin_src ruby
135
+ class Person
136
+ include Mongoid::Document
137
+ extend Search::ClassMethods
138
+
139
+ if Search::Client.instance.enabled?
140
+ after_create :add_to_search
141
+ after_update :update_in_search
142
+ after_destroy :remove_from_search
143
+ end
144
+
145
+ # normal Mongoid attributes
146
+ field :name, type: String
147
+ field :description, type: String
148
+ field :age, type: Integer
149
+ end
150
+ #+end_src
151
+
152
+ Note that that /unless you configure it otherwise/ the ids of
153
+ =belongs_to= objects will not be searchable. This is because they're
154
+ random strings that no human's ever going to be searching for, and we
155
+ don't want to waste RAM or storage.
156
+
157
+ *** Going Beyond The Defaults
158
+ This module strives for sensible defaults, but you can override them
159
+ with the following optional constants:
160
+
161
+ - =PRIMARY_SEARCH_KEY= - a Symbol matching one of your model's
162
+ attributes that is guaranteed unique. This defaults to =_id=
163
+ - =SEARCH_INDEX_NAME= - a String - useful if you want to have records
164
+ from multiple classes come back in the same search results. This
165
+ defaults to the underscored form of the current class name.
166
+ - =SEARCH_OPTIONS= - a hash of key value pairs in JS style
167
+ - See the
168
+ [[https://www.meilisearch.com/docs/reference/api/search#search-parameters][meilisearch search parameter docs]] for details.
169
+ - example from
170
+ [[https://github.com/meilisearch/meilisearch-ruby/blob/main/spec/meilisearch/index/search/multi_params_spec.rb][meliesearch's multi_param_spec]]
171
+
172
+ #+begin_src ruby
173
+ {
174
+ attributesToCrop: ['title'],
175
+ cropLength: 2,
176
+ filter: 'genre = adventure',
177
+ attributesToHighlight: ['title'],
178
+ limit: 2
179
+ }
180
+ #+end_src
181
+
182
+ - =SEARCH_RANKING_RULES= - an array of strings that correspond to
183
+ meilisearch rules see
184
+ [[https://www.meilisearch.com/docs/learn/core_concepts/relevancy#ranking-rules][meilisearch ranking rules docs]] You probably don't want to change this.
185
+
186
+ ** Indexes
187
+ Searching is limited to records that have been added to a given index.
188
+ This means, if you want to perform one search and get back records from
189
+ multiple models you'll need to add them to the same index.
190
+
191
+ In order to do that add the =SEARCH_INDEX_NAME= constant to the model
192
+ whose search stuff you want to end up in the same index. You can name
193
+ this just about anything. The important thing is that all the models
194
+ that share this index have the same =SEARCH_INDEX_NAME= constant
195
+ defined. You may want to just add it to a module they all import.
196
+
197
+ #+begin_src ruby
198
+ SEARCH_INDEX_NAME='general_search'
199
+ #+end_src
200
+
201
+ If multiple models are using the same index, you should also add
202
+ =CLASS_PREFIXED_SEARCH_IDS=true=. This causes the =id= field to be
203
+ =<ClassName>_<_id>= For example, a =Note= record might have an index of
204
+ ="Note_64274543906b1d7d02c1fcc6"=. If undefined this will default to
205
+ =false=. This is not needed if you can absolutely guarantee that there
206
+ will be no overlap in ids amongst all the models using a shared index.
207
+
208
+ #+begin_src ruby
209
+ CLASS_PREFIXED_SEARCH_IDS=true
210
+ #+end_src
211
+
212
+ Setting =CLASS_PREFIXED_SEARCH_IDS= to =true= will also cause the
213
+ original Mongoid =_id= field to be indexed as =original_document_id=.
214
+ This is useful if you want to be able to retrieve the original record
215
+ from the database.
216
+
217
+ *** Searchable Data
218
+ You probably don't want to index /all/ the fields. For example, unless
219
+ you intend to allow users to sort by when a record was created, there's
220
+ no point in recording it's =created_at= in the search index. It'll just
221
+ waste bandwidth, memory, and disk space.
222
+
223
+ Define a =SEARCHABLE_ATTRIBUTES= constant with an array of strings to
224
+ limit things. These are the field names, and/or names of methods you
225
+ wish to have indexed.
226
+
227
+ By default these will /also/ be the fields you can filter on.
228
+
229
+ Note that Meilisearch requires there to be an =id= field and it must be
230
+ a string. If you don't define one it will use string version of the
231
+ =_id= your document's =BSON::ObjectId=.
232
+
233
+ #+begin_src ruby
234
+ # explicitly define the fields you want to be searchable
235
+ # this should be an array of symbols
236
+ SEARCHABLE_ATTRIBUTES = %w[title body]
237
+ # OR explicitly define the fields you DON'T want searchable
238
+ SEARCHABLE_ATTRIBUTES = searchable_attributes - [:created_at]
239
+ #+end_src
240
+
241
+ **** Including Foreign Key data
242
+ If, for example, your =Person= =belongs_to: group= and you wanted that
243
+ group's id to be searchable you would include =group_id= in the list.
244
+
245
+ If you don't specify any =SEARCHABLE_ATTRIBUTES=, the default list will
246
+ exclude any fields that are =Mongoid::Fields::ForeignKey= objects.
247
+
248
+ **** Getting Extra Specific
249
+ If your searchable data needs to by dynamically generated instead of
250
+ just taken directly from the =Mongoid::Document='s attributes or
251
+ existing methods you can define a =search_indexable_hash= method on your
252
+ class.
253
+
254
+ Before you do, please note that as of v1.1 your =SEARCHABLE_ATTRIBUTES=
255
+ constant can contain fields and method names in its array of values.
256
+ Making a method for each thing dynamically generated thing you want in
257
+ the search and then including it in SEARCHABLE_ATTRIBUTES is going to be
258
+ the easiest way of accomplishing this.
259
+
260
+ Your =search_indexable_hash= must return a hash, and that hash must
261
+ include the following keys: - ="id"= - a string that uniquely identifies
262
+ the record - ="object_class"= the name of the class that this record
263
+ corresponds to.
264
+
265
+ The value of ="object_class"= is usually just =self.class.name=. This is
266
+ something specific to this gem, and not Meilisearch itself.
267
+
268
+ See =InstanceMethods#search_indexable_hash= for an example.
269
+
270
+ **** Filterable Fields
271
+ If you'd like to only be able to filter on a subset of those then you
272
+ can define =FILTERABLE_ATTRIBUTE_NAMES= but it /must/ be a subset of
273
+ =SEARCHABLE_ATTRIBUTES=. This is enforced by the gem to guarantee no
274
+ complaints from Meilisearch. These must be symbols.
275
+
276
+ If you have no direct need for filterable results, set
277
+ =UNFILTERABLE_IN_SEARCH=true= in your model. This will save on index
278
+ size and speed up indexing, but you won't be able to filter search
279
+ results, and that's half of what makes Meilisearch so great. It should
280
+ be noted, that even if this /is/ set to =true= this gem will still add
281
+ ="object_class"= as a filterable attribute.
282
+
283
+ This is the magic that allows you to have an index shared by multiple
284
+ models and still be able to retrieve results specifically for one.
285
+
286
+ If you decide to re-enable filtering you can remove that constant, or
287
+ set it to false. Then call the following. If
288
+ =FILTERABLE_ATTRIBUTE_NAMES= is defined it will use that, otherwise it
289
+ will use whatever =.searchable_attributes= returns.
290
+
291
+ #+begin_src ruby
292
+ MyModel.set_filterable_attributes! # synchronous
293
+ MyModel.set_filterable_attributes # asynchronous
294
+ #+end_src
295
+
296
+ This will cause Meilisearch to reindex all the records for that index.
297
+ If you have a large number of records this could take a while. Consider
298
+ running it on a background thread. Note that filtering is managed at the
299
+ index level, not the individual record level. By setting filterable
300
+ attributes you're giving Meilisearch guidance on what to do when
301
+ indexing your data.
302
+
303
+ Note that you will encounter problems in a shared index if you try and
304
+ filter on a field that one of the contributing models doesn't have set
305
+ as a filterable field, or doesn't have at all.
306
+
307
+ *** Sortable Fields
308
+ Sortable fields work in essentially the same way as filterable fields.
309
+ By default it's the same as your =FILTERABLE_ATTRIBUTE_NAMES= which, in
310
+ turn, defaults to your =SEARCHABLE_ATTRIBUTES= You can override it by
311
+ setting =SORTABLE_ATTRIBUTE_NAMES=.
312
+
313
+ Note that you will encounter problems in a shared index if you try and
314
+ sort on a field that one of the contributing models doesn't have set as
315
+ a sortable field, or doesn't have at all.
316
+
317
+ #+begin_src ruby
318
+ MyModel.set_sortable_attributes! # synchronous
319
+ MyModel.set_sortable_attributes # asynchronous
320
+ #+end_src
321
+
322
+ *** Indexing things
323
+ *Important note*: By default anything you do that updates the search
324
+ index (adding, removing, or changing) happens asynchronously.
325
+
326
+ Sometimes, especially when debugging something on the console, you want
327
+ to update the index /synchronously/. The convention used in this
328
+ codebase - and in the meilisearch-ruby library we build on - is that the
329
+ synchronous methods are the ones with the bang. Similar to how mutating
330
+ state is potentially dangerous and noted with a bang, using synchronous
331
+ methods is potentially problematic for your users, and thus noted with a
332
+ bang.
333
+
334
+ For example:
335
+
336
+ #+begin_src ruby
337
+ MyModel.reindex # runs asyncronously
338
+ #+end_src
339
+
340
+ vs
341
+
342
+ #+begin_src ruby
343
+ MyModel.reindex! # runs synchronously
344
+ #+end_src
345
+
346
+ **** Reindexing, Adding, Updating, and Deleting
347
+ *Reindexing*
348
+ Calling =MyModel.reindex!= deletes all the existing records from the
349
+ current index, and then reindexes all the records for the current model.
350
+ It's safe to run this even if there aren't any records. In addition to
351
+ re-indexing your models, it will update/set the "sortable" and
352
+ "filterable" fields on the relevant indexes.
353
+
354
+ Note: reindexing behaves slightly differently than all the other
355
+ methods. It runs semi-asynchronously by default. The Asynchronous form
356
+ will first, attempt to /synchronously/ delete all the records from the
357
+ index. If that fails an exception will be raised. Otherwise you'd think
358
+ everything was fine when actually it had failed miserably. If you call
359
+ =.reindex!= it will be entirely synchronous.
360
+
361
+ Note: adding, updating, and deleting should happen automatically if
362
+ you've defined =after_create=, =after_update=, and =after_destroy= as
363
+ instructed above. You'll mostly only want to use these when manually
364
+ mucking with things in the console.
365
+
366
+ *Adding*
367
+ Be careful to not add documents that are already in the index.
368
+
369
+ - Add everything: =MyClass.add_all_to_search=
370
+ - Add a specific instance: =my_instance.add_to_search=
371
+ - Add a specific subset of documents:
372
+ =MyClass.add_documents(documents_hashes)= IMPORTANT:
373
+ =documents_hashes= must be an array of hashes that were each generated
374
+ via =search_indexable_hash=
375
+
376
+ *Updating*
377
+ - Update everything: call =reindex=
378
+ - Update a specific instance: =my_instance.update_in_search=
379
+ - Update a specific subset of documents: =MyClass.update_documents(documents_hashes)= IMPORTANT: =documents_hashes= must be an array of hashes that
380
+ were generated via =search_indexable_hash= The =PRIMARY_SEARCH_KEY=
381
+ (=_id= by default) will be used to find records in the index to update.
382
+
383
+ *Deleting*
384
+ - Delete everything: =MyClass.delete_all_documents!=
385
+ - Delete a specific record: =my_instance.remove_from_search=
386
+ - Delete the index: =MyClass.delete_index!=
387
+ WARNING: if you think you should use this, /you're probably mistaken/.
388
+
389
+ **** Indexes
390
+ By default every model gets its own search index. This means that
391
+ =Foo.search("some text")= will only search =Foo= objects. To have a
392
+ search cross objects you'll need to use a "Shared Index" (see below).
393
+
394
+ The name of the index isn't important when not using shared indexes. By
395
+ default a model's index is the snake cased form of the class name. For
396
+ example, data for =MyWidget= models will be stored in the =my_widget=
397
+ index.
398
+
399
+ **** Shared indexes
400
+ Imagine you have a =Note= and a =Comment= model, sharing an index so
401
+ that you can perform a single search and have search results for both
402
+ models that are ranked by relevance.
403
+
404
+ In this case both models would define a =SEARCH_INDEX_NAME= constant
405
+ with the same value. You might want to just put this, and the other
406
+ search stuff in a common module that they all =include=.
407
+
408
+ Then, when you search you can say =Note.search("search term")= and it
409
+ will /only/ bring back results for =Note= records. If you want to
410
+ include results that match =Comment= records too, you can set the
411
+ optional =filtered_by_class= parameter to =false=.
412
+
413
+ For example: =Note.search("search term", filtered_by_class: false)= will
414
+ return all matching =Note= results, as well as results for /all/ the
415
+ other models that share the same index as =Note=.
416
+
417
+ ⚠ Models sharing the same index must share the same primary key field as
418
+ well. This is a known limitation of the system.
419
+
420
+ ** Searching
421
+ To get a list of all the matching objects in the order returned by the
422
+ search engine run =MyModel.search("search term")= Note that this will
423
+ restrict the results to records generated by the model you're calling
424
+ this on. If you have an index that contains data from multiple models
425
+ and wish to include all of them in the results pass in the optional
426
+ =filtered_by_class= parameter with a =false= value. E.g.
427
+ =MyModel.search("search term", filtered_by_class: false)=
428
+
429
+ Searching returns a hash, with the class name of the results as the key
430
+ and an array of String ids, or =Mongoid::Document= objects as the value.
431
+ By default it assumes you want =Mongoid::Document= objects. The returned
432
+ hash /also/ includes a key of ="search_result_metadata"= which includes
433
+ the metadata provided by Meilisearch regarding your request. You'll need
434
+ this for pagination if you have lots of results. To /exclude/ the
435
+ metadata pass =include_metadata: false= as an option. E.g.
436
+ =MyModel.search("search term", include_metadata: false)=
437
+
438
+ *** Useful Keyword Parameters
439
+ - =ids_only=
440
+ - only return matching ids. These will be an array under the
441
+ ="matches"= key.
442
+ - defaults to =false=
443
+ - =filtered_by_class=
444
+ - limit results to the class you initiated the search from. E.g.
445
+ =Note.search("foo")= will only return results from the =Note= class
446
+ even if there are records from other classes in the same index.
447
+ - defaults to =true=
448
+ - =include_metadata=
449
+ - include the metadata about the search results provided by
450
+ Meilisearch. If true (default) there will be a
451
+ ="search_result_metadata"= key, with a hash of the Meilisearch
452
+ metadata.
453
+ - You'll likely need this in order to support pagination, however if
454
+ you just want to return a single page worth of data, you can set
455
+ this to =false= to discard it.
456
+ - defaults to =true=
457
+
458
+ *** Example Search Results
459
+ Search results, ids only, for a class where
460
+ =CLASS_PREFIXED_SEARCH_IDS=false=.
461
+
462
+ #+begin_src ruby
463
+ Note.search('foo', ids_only: true) # => returns
464
+ {
465
+ "matches" => [
466
+ "64274a5d906b1d7d02c1fcc7",
467
+ "643f5e1c906b1d60f9763071",
468
+ "64483e63906b1d84f149717a"
469
+ ],
470
+ "search_result_metadata" => {
471
+ "query"=>query_string,
472
+ "processingTimeMs"=>1,
473
+ "limit"=>50,
474
+ "offset"=>0,
475
+ "estimatedTotalHits"=>33,
476
+ "nbHits"=>33
477
+ }
478
+ }
479
+ #+end_src
480
+
481
+ If =CLASS_PREFIXED_SEARCH_IDS=true= the above would have ids like
482
+ ="Note_64274a5d906b1d7d02c1fcc7"=
483
+
484
+ Without =ids_only= you get full objects in a =matches= array.
485
+
486
+ #+begin_src ruby
487
+ Note.search('foo') # or Note.search('foo', ids_only: false) # => returns
488
+ {
489
+ "matches" => [
490
+ #<Note _id: 64274a5d906b1d7d02c1fcc7, created_at: 2023-03-15 00:00:00 UTC, updated_at: 2023-03-31 21:02:21.108 UTC, title: "A note from the past", body: "a body", type: "misc", context: "dachary">,
491
+ #<Note _id: 643f5e1c906b1d60f9763071, created_at: 2023-04-18 00:00:00 UTC, updated_at: 2023-04-19 03:21:00.41 UTC, title: "offline standup ", body: "onother body", type: "misc", context: "WORK">,
492
+ #<Note _id: 64483e63906b1d84f149717a, created_at: 2023-04-25 00:00:00 UTC, updated_at: 2023-04-26 11:23:38.125 UTC, title: "Standup Notes (for wed)", body: "very full bodied", type: "misc", context: "WORK">
493
+ ],
494
+ "search_result_metadata" => {
495
+ "query"=>query_string, "processingTimeMs"=>1, "limit"=>50,
496
+ "offset"=>0, "estimatedTotalHits"=>33, "nbHits"=>33
497
+ }
498
+ }
499
+ #+end_src
500
+
501
+ If =Note= records shared an index with =Task= and they both had
502
+ =CLASS_PREFIXED_SEARCH_ID=true= you'd get a result like this.
503
+
504
+ #+begin_src ruby
505
+ Note.search('foo') #=> returns
506
+ {
507
+ "matches" => [
508
+ #<Note _id: 64274a5d906b1d7d02c1fcc7, created_at: 2023-03-15 00:00:00 UTC, updated_at: 2023-03-31 21:02:21.108 UTC, title: "A note from the past", body: "a body", type: "misc", context: "dachary">,
509
+ #<Note _id: 643f5e1c906b1d60f9763071, created_at: 2023-04-18 00:00:00 UTC, updated_at: 2023-04-19 03:21:00.41 UTC, title: "offline standup ", body: "onother body", type: "misc", context: "WORK">,
510
+ #<Task _id: 64483e63906b1d84f149717a, created_at: 2023-04-25 00:00:00 UTC, updated_at: 2023-04-26 11:23:38.125 UTC, title: "Do the thing", body: "very full bodied", type: "misc", context: "WORK">
511
+ ],
512
+ "search_result_metadata" => {
513
+ "query"=>query_string, "processingTimeMs"=>1, "limit"=>50,
514
+ "offset"=>0, "estimatedTotalHits"=>33, "nbHits"=>33
515
+ }
516
+
517
+ }
518
+ #+end_src
519
+
520
+ *** Custom Search Options
521
+ To invoke any of Meilisearch's custom search options (see
522
+ [[https://www.meilisearch.com/docs/reference/api/search][their
523
+ documentation]]). You can pass them in via an options hash.
524
+
525
+ =MyModel.search("search term", options: <my custom options>)=
526
+
527
+ Currently the Meilisearch-ruby gem can convert keys from snake case to
528
+ camel case. For example =hits_per_page= will become =hitsPerPage=.
529
+ Meilisearch ultimately wants camel case (=camelCase=) parameter keys,
530
+ /but/ =meilisearch-ruby= wants snake case (=snake_case=).
531
+
532
+ Follow Meilisearch's documentation to see what's available and what type
533
+ of options to pass it, but convert them to snake case first. Note that
534
+ your options keys and values must all be simple JSON values.
535
+
536
+ If for some reason that still isn't enough, you can work with the
537
+ meilisearch-ruby index directly via
538
+ =Search::Client.instance.index(search_index_name)=
539
+
540
+ **** Pagination
541
+ This gem has no specific pagination handling, as there are multiple
542
+ libraries for handling pagination in Ruby. Here's an example of how to
543
+ get started with [[https://github.com/ddnexus/pagy][Pagy]].
544
+
545
+ #+begin_src ruby
546
+ current_page_number = 1
547
+ max_items_per_page = 10
548
+
549
+ search_results = Note.search('foo')
550
+
551
+ Pagy.new(
552
+ count: search_results["search_result_metadata"]["nbHits"],
553
+ page: current_page_number,
554
+ items: max_items_per_page
555
+ )
556
+ #+end_src
557
+
558
+ ** Upgrading
559
+ *** From v1.x
560
+ primary difference between v1.x and v2 is that =mongodb_meilisearch= no longer uses the Meilisearch master key [[https://www.meilisearch.com/docs/learn/security/basic_security][for security reasons]]. The /only/ thing it will use it for is to look up the default search API key and / or the default administrative API key.
561
+
562
+ It now initializes 2 =Meilisearch::Client= objects: one for searching, one for administration, and all of the internal methods that interact with an index have been rewritten to interact with it via the correct client.
563
+
564
+
565
+ Practically speaking, there are 3 significant differences from v1.x
566
+
567
+ 1. =MEILISEARCH_API_KEY= is no longer supported. It has been replaced with the official Meilisearch environment variable for the same purpose: =MEILI_MASTER_KEY=
568
+ 2. The master key is only used when an API key for search and / or administration is not provided. It is recommended that you provide =MEILISEARCH_SEARCH_KEY= & =MEILISEARCH_ADMIN_KEY= for those purposes. See "Finding your Search & Admin keys" above for more details.
569
+ 3. =Search::Client.instance.client= has been replaced with 2 clients with specific purposes
570
+ - =Search::Client::instance.search_client=
571
+ - =Search::Client::instance.admin_client=
572
+ 4. =ClassMethods.search_index= has been replaced with
573
+ - =ClassMethods.searchable_index=
574
+ - =ClassMethods.administratable_index=
575
+
576
+ ** Development
577
+ To contribute to this gem.
578
+
579
+ - Run =bundle install= to install all the dependencies.
580
+ - run =lefthook install= to set up
581
+ [[https://github.com/evilmartians/lefthook][lefthook]] This will do
582
+ things like make sure the tests still pass, and run rubocop before you
583
+ commit.
584
+ - Start hacking.
585
+ - Add RSpec tests.
586
+ - Add your name to CONTRIBUTORS.md
587
+ - Make PR.
588
+
589
+ NOTE: by contributing to this repository you are offering to transfer
590
+ copyright to the current maintainer of the repository.
591
+
592
+ To install this gem onto your local machine, run
593
+ =bundle exec rake install=. To release a new version, update the version
594
+ number in =version.rb=, and then run =bundle exec rake release=, which
595
+ will create a git tag for the version, push git commits and the created
596
+ tag, and push the =.gem= file to [[https://rubygems.org][rubygems.org]].
597
+
598
+ Bug reports and pull requests are welcome on GitHub at
599
+ https://github.com/masukomi/mongodb_meilisearch. This project is
600
+ intended to be a safe, welcoming space for collaboration, and
601
+ contributors are expected to adhere to the
602
+ [[https://github.com/masukomi/mongodb_meilisearch/blob/main/CODE_OF_CONDUCT.md][code of conduct]].
603
+
604
+ ** License
605
+ The gem is available as open source under the terms of the
606
+ [[https://github.com/masukomi/mongodb_meilisearch/blob/main/LICENSE.txt][Server Side Public License]]. For those unfamiliar, the short version is that
607
+ if you use it in a server side app you need to share all the code for
608
+ that app and its infrastructure. It's like AGPL on steroids. Commercial
609
+ licenses are available if you want to use this in a commercial setting
610
+ but not share all your source.
611
+
612
+ ** Code of Conduct
613
+ Everyone interacting in this project's codebases, issue trackers, chat
614
+ rooms and mailing lists is expected to follow the
615
+ [[https://github.com/masukomi/mongodb_meilisearch/blob/main/CODE_OF_CONDUCT.md][code
616
+ of conduct]].
617
+
618
+ * Footnotes
619
+ [fn:macos] MacOS users I have not found a way to successfully automatically Meilisearch with launchctl (brew services uses this) that allows it to see the ~MEILI_MASTER_KEY~ environment variable. As such, I can /not/ recommend using launchctl for running Meilisearch. Running it without a master key only works in development mode, and introduces complications.
620
+
621
+ [fn:default_keys] Using the master key you can access the [[https://www.meilisearch.com/docs/reference/api/keys][/keys]] endpoint in Meilisearch to retrieve the default Search & Administrative API keys
@@ -5,5 +5,5 @@ module MongodbMeilisearch
5
5
  # @note This library will adhere to strict semantic versioning.
6
6
  # See https://semver.org/
7
7
  #
8
- VERSION = "1.3.0"
8
+ VERSION = "2.0.0"
9
9
  end