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