mongodb_meilisearch 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -6
- data/Gemfile.lock +1 -1
- data/README.md +41 -7
- data/lib/mongodb_meilisearch/version.rb +1 -1
- data/lib/search/class_methods.rb +109 -16
- data/lib/search/client.rb +1 -1
- data/lib/search/instance_methods.rb +2 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7873b1a21e75522aa4fdaa063231997319faae90ddb9a4774cd814bf18a4afc
|
4
|
+
data.tar.gz: b4f241003a1ff709c1247f6411cec39831459795d8e17c429a9b7b187ff790cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ad38b1b7f7c14baaee4c12491affeb62c9b9a700ec41d3bddcdf3c095288c084c9bf8f6c9feb28300b441e5fd94380179c9f334c7d2df078d48410221fbc05f
|
7
|
+
data.tar.gz: 5792131d06698d2af16194078e1d5a8bfbf37b9084502f25183d5f3c347d712d2eeac7e604edf0298800a03795e573336adbdc9a5b1d47929691f9288e974027
|
data/.rubocop.yml
CHANGED
@@ -1116,12 +1116,6 @@ Style/ComparableClamp:
|
|
1116
1116
|
Style/ConcatArrayLiterals:
|
1117
1117
|
Enabled: false
|
1118
1118
|
|
1119
|
-
Style/ConditionalAssignment:
|
1120
|
-
Enabled: true
|
1121
|
-
EnforcedStyle: assign_to_condition
|
1122
|
-
SingleLineConditionsOnly: true
|
1123
|
-
IncludeTernaryExpressions: true
|
1124
|
-
|
1125
1119
|
Style/ConstantVisibility:
|
1126
1120
|
Enabled: false
|
1127
1121
|
|
@@ -1857,3 +1851,11 @@ Style/YodaExpression:
|
|
1857
1851
|
Style/ZeroLengthPredicate:
|
1858
1852
|
Enabled: false
|
1859
1853
|
|
1854
|
+
# this is horrible for readability
|
1855
|
+
# and maintainability
|
1856
|
+
Style/ConditionalAssignment:
|
1857
|
+
Enabled: false
|
1858
|
+
|
1859
|
+
# I'll nest how I wanna nest! :p
|
1860
|
+
RSpec/NestedGroups:
|
1861
|
+
Enabled: false
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -226,7 +226,8 @@ Then call the following. If `FILTERABLE_ATTRIBUTE_NAMES` is defined it will use
|
|
226
226
|
otherwise it will use whatever `.searchable_attributes` returns.
|
227
227
|
|
228
228
|
```ruby
|
229
|
-
MyModel.set_filterable_attributes!
|
229
|
+
MyModel.set_filterable_attributes! # synchronous
|
230
|
+
MyModel.set_filterable_attributes # asynchronous
|
230
231
|
```
|
231
232
|
|
232
233
|
This will cause Meilisearch to reindex all the records for that index. If you
|
@@ -235,14 +236,31 @@ on a background thread. Note that filtering is managed at the index level, not t
|
|
235
236
|
record level. By setting filterable attributes you're giving Meilisearch
|
236
237
|
guidance on what to do when indexing your data.
|
237
238
|
|
239
|
+
Note that you will encounter problems in a shared index if you try and
|
240
|
+
filter on a field that one of the contributing models doesn't have set
|
241
|
+
as a filterable field, or doesn't have at all.
|
238
242
|
|
243
|
+
### Sortable Fields
|
244
|
+
|
245
|
+
Sortable fields work in essentially the same way as filterable fields.
|
246
|
+
By default it's the same as your `FILTERABLE_ATTRIBUTE_NAMES` which, in turn, defaults to your `SEARCHABLE_ATTRIBUTES` You can
|
247
|
+
override it by setting `SORTABLE_ATTRIBUTE_NAMES`.
|
248
|
+
|
249
|
+
Note that you will encounter problems in a shared index if you try and
|
250
|
+
sort on a field that one of the contributing models doesn't have set
|
251
|
+
as a sortable field, or doesn't have at all.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
MyModel.set_sortable_attributes! # synchronous
|
255
|
+
MyModel.set_sortable_attributes # asynchronous
|
256
|
+
```
|
239
257
|
|
240
258
|
### Indexing things
|
241
259
|
**Important note**: By default anything you do that updates the search index (adding, removing, or changing) happens asynchronously.
|
242
260
|
|
243
261
|
Sometimes, especially when debugging something on the console, you want to
|
244
|
-
update the index _synchronously_. The convention used in this codebase is that
|
245
|
-
the synchronous methods
|
262
|
+
update the index _synchronously_. The convention used in this codebase - and in the meilisearch-ruby library we build on - is that
|
263
|
+
the synchronous methods are the ones with the bang. Similar to how mutating
|
246
264
|
state is potentially dangerous and noted with a bang, using synchronous methods
|
247
265
|
is potentially problematic for your users, and thus noted with a bang.
|
248
266
|
|
@@ -264,7 +282,9 @@ MyModel.reindex! # runs synchronously
|
|
264
282
|
**Reindexing**
|
265
283
|
Calling `MyModel.reindex!` deletes all the existing records from the current index,
|
266
284
|
and then reindexes all the records for the current model. It's safe to run this
|
267
|
-
even if there aren't any records.
|
285
|
+
even if there aren't any records. In addition to re-indexing your models,
|
286
|
+
it will update/set the "sortable" and "filterable" fields on the
|
287
|
+
relevant indexes.
|
268
288
|
|
269
289
|
Note: reindexing behaves slightly differently than all the other methods.
|
270
290
|
It runs semi-asynchronously by default. The Asynchronous form will first,
|
@@ -303,6 +323,16 @@ Be careful to not add documents that are already in the index.
|
|
303
323
|
WARNING: if you think you should use this, you're probably
|
304
324
|
mistaken.
|
305
325
|
|
326
|
+
#### Indexes
|
327
|
+
By default every model gets its own search index. This means that
|
328
|
+
`Foo.search("some text")` will only search `Foo` objects. To have a
|
329
|
+
search cross objects you'll need to use a "Shared Index" (see below).
|
330
|
+
|
331
|
+
|
332
|
+
The name of the index isn't important when not using shared indexes.
|
333
|
+
By default a model's index is the snake cased form of the class name.
|
334
|
+
For example, data for `MyWidget` models will be stored in the `my_widget` index.
|
335
|
+
|
306
336
|
#### Shared indexes
|
307
337
|
Imagine you have a `Note` and a `Comment` model, sharing an index so that
|
308
338
|
you can perform a single search and have search results for both models
|
@@ -422,10 +452,14 @@ To invoke any of Meilisearch's custom search options (see [their documentation](
|
|
422
452
|
|
423
453
|
`MyModel.search("search term", options: <my custom options>)`
|
424
454
|
|
425
|
-
|
455
|
+
Currently the Meilisearch-ruby gem can convert keys from snake case to
|
426
456
|
camel case. For example `hits_per_page` will become `hitsPerPage`.
|
427
|
-
Meilisearch ultimately wants camel case
|
428
|
-
|
457
|
+
Meilisearch ultimately wants camel case (`camelCase`) parameter keys,
|
458
|
+
_but_ `meilisearch-ruby` wants snake case (`snake_case`).
|
459
|
+
|
460
|
+
Follow Meilisearch's documentation
|
461
|
+
to see what's available and what type of options to pass it, but convert
|
462
|
+
them to snake case first. Note that your
|
429
463
|
options keys and values must all be simple JSON values.
|
430
464
|
|
431
465
|
If for some reason that still isn't enough, you can work with the
|
data/lib/search/class_methods.rb
CHANGED
@@ -109,6 +109,7 @@ module Search
|
|
109
109
|
# @param filtered_by_class [Boolean] - defaults to filtering results by the class
|
110
110
|
# your searching from. Ex. Foo.search("something") it will
|
111
111
|
# have Meilisearch filter on records where `object_class` == `"Foo"`
|
112
|
+
# This simplifies working with shared indexes.
|
112
113
|
# @return [Hash[String, [Array[String | Document]] a hash with keys corresponding
|
113
114
|
# to the classes of objects returned, and a value of an array of ids or
|
114
115
|
# Mongoid::Document objects sorted by match strength. It will ALSO have a key named
|
@@ -135,13 +136,13 @@ module Search
|
|
135
136
|
|
136
137
|
# TODO: break this if/else out into a separate function
|
137
138
|
if ids_only
|
138
|
-
options.merge!({
|
139
|
+
options.merge!({attributes_to_retrieve: [pk.to_s]})
|
139
140
|
else
|
140
141
|
# Don't care what you add, but we need the primary key and object_class
|
141
|
-
options[:
|
142
|
+
options[:attributes_to_retrieve] = [] unless options.has_key?(:attributes_to_retrieve)
|
142
143
|
[pk.to_s, "object_class"].each do |attr|
|
143
|
-
unless options[:
|
144
|
-
options[:
|
144
|
+
unless options[:attributes_to_retrieve].include?(attr)
|
145
|
+
options[:attributes_to_retrieve] << attr
|
145
146
|
end
|
146
147
|
end
|
147
148
|
end
|
@@ -260,6 +261,7 @@ module Search
|
|
260
261
|
# - each document hash is presumed to have been created by way of
|
261
262
|
# search_indexable_hash
|
262
263
|
def add_documents(new_documents, async: true)
|
264
|
+
configure_attributes_and_index_if_needed!
|
263
265
|
if async
|
264
266
|
search_index.add_documents(new_documents, primary_search_key)
|
265
267
|
else
|
@@ -378,28 +380,113 @@ module Search
|
|
378
380
|
# @return [Array[Symbol]] an array of symbols corresponding to
|
379
381
|
# filterable attribute names.
|
380
382
|
def filterable_attributes
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
383
|
+
@_filterable_attributes ||= sort_or_filter_attributes(:filterable)
|
384
|
+
end
|
385
|
+
|
386
|
+
def sortable_attributes
|
387
|
+
@_sortable_attributes ||= sort_or_filter_attributes(:sortable)
|
388
|
+
end
|
389
|
+
|
390
|
+
# For more details on sortable attributes see the official
|
391
|
+
# Meilisearch docs
|
392
|
+
# https://www.meilisearch.com/docs/reference/api/settings#sortable-attributes
|
393
|
+
#
|
394
|
+
#
|
395
|
+
# @return [Array] - an array of attributes configured as sortable
|
396
|
+
# in the index.
|
397
|
+
def meilisearch_sortable_attributes
|
398
|
+
@_meilisearch_sortable_attributes ||= search_index.get_sortable_attributes
|
399
|
+
end
|
400
|
+
|
401
|
+
def meilisearch_filterable_attributes
|
402
|
+
@_meilisearch_filterable_attributes ||= search_index.get_filterable_attributes
|
403
|
+
end
|
404
|
+
|
405
|
+
def reset_cached_data!
|
406
|
+
@_meilisearch_filterable_attributes = nil
|
407
|
+
@_filterable_attributes = nil
|
408
|
+
@_meilisearch_sortable_attributes = nil
|
409
|
+
@_sortable_attributes = nil
|
410
|
+
end
|
411
|
+
|
412
|
+
# Guarantees that the filterable attributes have been configured
|
413
|
+
# This should be called before
|
414
|
+
def configure_attributes_and_index_if_needed!
|
415
|
+
return if unfilterable?
|
416
|
+
indexes_filterable_attributes = []
|
417
|
+
|
418
|
+
begin
|
419
|
+
indexes_filterable_attributes = meilisearch_filterable_attributes
|
420
|
+
rescue ::MeiliSearch::ApiError => e
|
421
|
+
# this is expected to happen the first time an instance
|
422
|
+
# of a new model is saved.
|
423
|
+
raise unless e.message.match?(/Index `\S+` not found\./)
|
424
|
+
Search::Client.instance.create_index(search_index_name)
|
388
425
|
end
|
389
|
-
|
390
|
-
|
426
|
+
|
427
|
+
return if indexes_filterable_attributes.include?("object_class")
|
428
|
+
set_filterable_attributes
|
429
|
+
set_sortable_attributes
|
391
430
|
end
|
392
431
|
|
393
432
|
# Updates the filterable attributes in the search index.
|
394
433
|
# Note that this forces Meilisearch to rebuild your index,
|
395
434
|
# which may take time. Best to run this in a background job
|
396
435
|
# for large datasets.
|
397
|
-
def set_filterable_attributes
|
436
|
+
def set_filterable_attributes(new_attributes = filterable_attributes)
|
398
437
|
search_index.update_filterable_attributes(new_attributes)
|
399
438
|
end
|
400
439
|
|
440
|
+
def set_filterable_attributes!(new_attributes = filterable_attributes)
|
441
|
+
# meilisearch-ruby doesn't provide a synchronous version of this
|
442
|
+
task = set_filterable_attributes(new_attributes)
|
443
|
+
search_index.wait_for_task(task["taskUid"])
|
444
|
+
end
|
445
|
+
|
446
|
+
# Updates the sortable attributes in the search index.
|
447
|
+
# Note that this forces Meilisearch to rebuild your index,
|
448
|
+
# which may take time. Best to run this in a background job
|
449
|
+
# for large datasets.
|
450
|
+
def set_sortable_attributes(new_attributes = sortable_attributes)
|
451
|
+
search_index.update_sortable_attributes(new_attributes)
|
452
|
+
end
|
453
|
+
|
454
|
+
def set_sortable_attributes!(new_attributes = sortable_attributes)
|
455
|
+
# meilisearch-ruby doesn't provide a synchronous version of this
|
456
|
+
task = set_sortable_attributes(new_attributes)
|
457
|
+
search_index.wait_for_task(task["taskUid"])
|
458
|
+
end
|
459
|
+
|
401
460
|
private
|
402
461
|
|
462
|
+
# @param [Symbol] which - either :sortable or :filterable
|
463
|
+
def sort_or_filter_attributes(which)
|
464
|
+
constant_symbol = (which == :sortable) ? :SORTABLE_ATTRIBUTE_NAMES : :FILTERABLE_ATTRIBUTE_NAMES
|
465
|
+
|
466
|
+
if which == :filterable && unfilterable? && constants.include?(constant_symbol)
|
467
|
+
raise "You can't define FILTERABLE_ATTRIBUTE_NAMES & UNFILTERABLE_IN_SEARCH on #{self.class.name}"
|
468
|
+
end
|
469
|
+
# with that out of the way...
|
470
|
+
|
471
|
+
attributes = []
|
472
|
+
# sortable == defined or filterable
|
473
|
+
# filterable == defined or search
|
474
|
+
if constants.include?(constant_symbol)
|
475
|
+
# the union operator is to guarantee no-one tries to create
|
476
|
+
# invalid filterable attributes
|
477
|
+
attributes = const_get(constant_symbol).map(&:to_sym) & searchable_attributes
|
478
|
+
elsif which == :filterable && !unfilterable?
|
479
|
+
attributes = searchable_attributes
|
480
|
+
elsif which == :sortable
|
481
|
+
# yes this is recursive...
|
482
|
+
attributes = filterable_attributes
|
483
|
+
end
|
484
|
+
# we need object_class even if you're unfilterable, because
|
485
|
+
# we need to be able to filter by object_class in shared indexes.
|
486
|
+
attributes << :object_class unless attributes.include? :object_class
|
487
|
+
attributes
|
488
|
+
end
|
489
|
+
|
403
490
|
def lookup_matches_by_class!(results, primary_key)
|
404
491
|
# matches_by_class contains a hash of
|
405
492
|
# "FooModel" => [<array of ids>]
|
@@ -515,7 +602,7 @@ module Search
|
|
515
602
|
def reindex_core(async: true)
|
516
603
|
# no point in continuing if this fails...
|
517
604
|
delete_all_documents!
|
518
|
-
|
605
|
+
reset_cached_data!
|
519
606
|
# this conveniently lines up with the batch size of 100
|
520
607
|
# that Mongoid gives us
|
521
608
|
documents = []
|
@@ -528,7 +615,13 @@ module Search
|
|
528
615
|
end
|
529
616
|
add_documents(documents, async: async) if documents.size != 0
|
530
617
|
|
531
|
-
|
618
|
+
if async
|
619
|
+
set_filterable_attributes
|
620
|
+
set_sortable_attributes
|
621
|
+
else
|
622
|
+
set_filterable_attributes!
|
623
|
+
set_sortable_attributes!
|
624
|
+
end
|
532
625
|
end
|
533
626
|
end
|
534
627
|
end
|
data/lib/search/client.rb
CHANGED
@@ -2,6 +2,7 @@ module Search
|
|
2
2
|
module InstanceMethods
|
3
3
|
# Adds this record to the search index asynchronously
|
4
4
|
def add_to_search
|
5
|
+
self.class.configure_attributes_and_index_if_needed!
|
5
6
|
search_index.add_documents(
|
6
7
|
[search_indexable_hash],
|
7
8
|
primary_search_key.to_s
|
@@ -10,6 +11,7 @@ module Search
|
|
10
11
|
|
11
12
|
# Adds this record to the search index synchronously
|
12
13
|
def add_to_search!
|
14
|
+
self.class.configure_attributes_and_index_if_needed!
|
13
15
|
index = search_index
|
14
16
|
documents = [search_indexable_hash]
|
15
17
|
pk = primary_search_key.to_s
|
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.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- masukomi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -187,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
187
|
- !ruby/object:Gem::Version
|
188
188
|
version: '0'
|
189
189
|
requirements: []
|
190
|
-
rubygems_version: 3.4.
|
190
|
+
rubygems_version: 3.4.20
|
191
191
|
signing_key:
|
192
192
|
specification_version: 4
|
193
193
|
summary: MeiliSearch integration for MongoDB
|