meilisearch-rails 0.2.3 → 0.3.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 +4 -4
- data/Gemfile +23 -16
- data/README.md +20 -26
- data/Rakefile +4 -3
- data/lib/meilisearch/configuration.rb +5 -3
- data/lib/meilisearch/errors.rb +12 -0
- data/lib/meilisearch/pagination/kaminari.rb +8 -6
- data/lib/meilisearch/pagination/will_paginate.rb +3 -2
- data/lib/meilisearch/pagination.rb +5 -6
- data/lib/meilisearch/railtie.rb +2 -1
- data/lib/meilisearch/tasks/meilisearch.rake +8 -10
- data/lib/meilisearch/utilities.rb +9 -14
- data/lib/meilisearch/version.rb +3 -1
- data/lib/meilisearch-rails.rb +197 -180
- data/meilisearch-rails.gemspec +25 -35
- metadata +4 -5
- data/spec/spec_helper.rb +0 -52
- data/spec/utilities_spec.rb +0 -36
data/lib/meilisearch-rails.rb
CHANGED
@@ -2,6 +2,7 @@ require 'meilisearch'
|
|
2
2
|
|
3
3
|
require 'meilisearch/version'
|
4
4
|
require 'meilisearch/utilities'
|
5
|
+
require 'meilisearch/errors'
|
5
6
|
|
6
7
|
if defined? Rails
|
7
8
|
begin
|
@@ -19,11 +20,6 @@ end
|
|
19
20
|
require 'logger'
|
20
21
|
|
21
22
|
module MeiliSearch
|
22
|
-
|
23
|
-
class NotConfigured < StandardError; end
|
24
|
-
class BadConfiguration < StandardError; end
|
25
|
-
class NoBlockGiven < StandardError; end
|
26
|
-
|
27
23
|
autoload :Configuration, 'meilisearch/configuration'
|
28
24
|
extend Configuration
|
29
25
|
|
@@ -42,27 +38,26 @@ module MeiliSearch
|
|
42
38
|
include InstanceMethods
|
43
39
|
end
|
44
40
|
end
|
45
|
-
|
46
41
|
end
|
47
42
|
|
48
43
|
class IndexSettings
|
49
44
|
DEFAULT_BATCH_SIZE = 1000
|
50
45
|
|
51
|
-
DEFAULT_PRIMARY_KEY = 'id'
|
46
|
+
DEFAULT_PRIMARY_KEY = 'id'.freeze
|
52
47
|
|
53
48
|
# MeiliSearch settings
|
54
|
-
OPTIONS = [
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
]
|
49
|
+
OPTIONS = %i[
|
50
|
+
searchableAttributes
|
51
|
+
filterableAttributes
|
52
|
+
displayedAttributes
|
53
|
+
distinctAttribute
|
54
|
+
synonyms
|
55
|
+
stopWords
|
56
|
+
rankingRules
|
57
|
+
attributesToHighlight
|
58
|
+
attributesToCrop
|
59
|
+
cropLength
|
60
|
+
].freeze
|
66
61
|
|
67
62
|
OPTIONS.each do |option|
|
68
63
|
define_method option do |value|
|
@@ -84,40 +79,42 @@ module MeiliSearch
|
|
84
79
|
end
|
85
80
|
|
86
81
|
def attribute(*names, &block)
|
87
|
-
raise ArgumentError
|
82
|
+
raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1)
|
83
|
+
|
88
84
|
@attributes ||= {}
|
89
85
|
names.flatten.each do |name|
|
90
|
-
@attributes[name.to_s] = block_given? ?
|
86
|
+
@attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) }
|
91
87
|
end
|
92
88
|
end
|
93
|
-
alias
|
89
|
+
alias attributes attribute
|
94
90
|
|
95
91
|
def add_attribute(*names, &block)
|
96
|
-
raise ArgumentError
|
92
|
+
raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1)
|
93
|
+
|
97
94
|
@additional_attributes ||= {}
|
98
95
|
names.each do |name|
|
99
|
-
@additional_attributes[name.to_s] = block_given? ?
|
96
|
+
@additional_attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) }
|
100
97
|
end
|
101
98
|
end
|
102
|
-
alias
|
99
|
+
alias add_attributes add_attribute
|
103
100
|
|
104
|
-
def
|
101
|
+
def mongoid?(document)
|
105
102
|
defined?(::Mongoid::Document) && document.class.include?(::Mongoid::Document)
|
106
103
|
end
|
107
104
|
|
108
|
-
def
|
105
|
+
def sequel?(document)
|
109
106
|
defined?(::Sequel) && document.class < ::Sequel::Model
|
110
107
|
end
|
111
108
|
|
112
|
-
def
|
113
|
-
!
|
109
|
+
def active_record?(document)
|
110
|
+
!mongoid?(document) && !sequel?(document)
|
114
111
|
end
|
115
112
|
|
116
113
|
def get_default_attributes(document)
|
117
|
-
if
|
114
|
+
if mongoid?(document)
|
118
115
|
# work-around mongoid 2.4's unscoped method, not accepting a block
|
119
116
|
document.attributes
|
120
|
-
elsif
|
117
|
+
elsif sequel?(document)
|
121
118
|
document.to_hash
|
122
119
|
else
|
123
120
|
document.class.unscoped do
|
@@ -132,7 +129,7 @@ module MeiliSearch
|
|
132
129
|
|
133
130
|
def attributes_to_hash(attributes, document)
|
134
131
|
if attributes
|
135
|
-
|
132
|
+
attributes.map { |name, value| [name.to_s, value.call(document)] }.to_h
|
136
133
|
else
|
137
134
|
{}
|
138
135
|
end
|
@@ -141,22 +138,18 @@ module MeiliSearch
|
|
141
138
|
def get_attributes(document)
|
142
139
|
# If a serializer is set, we ignore attributes
|
143
140
|
# everything should be done via the serializer
|
144
|
-
if
|
141
|
+
if !@serializer.nil?
|
145
142
|
attributes = @serializer.new(document).attributes
|
146
|
-
|
147
|
-
|
143
|
+
elsif @attributes.blank?
|
144
|
+
attributes = get_default_attributes(document)
|
148
145
|
# no `attribute ...` have been configured, use the default attributes of the model
|
149
|
-
|
150
|
-
else
|
146
|
+
elsif active_record?(document)
|
151
147
|
# at least 1 `attribute ...` has been configured, therefore use ONLY the one configured
|
152
|
-
|
153
|
-
document.class.unscoped do
|
154
|
-
attributes = attributes_to_hash(@attributes, document)
|
155
|
-
end
|
156
|
-
else
|
148
|
+
document.class.unscoped do
|
157
149
|
attributes = attributes_to_hash(@attributes, document)
|
158
150
|
end
|
159
|
-
|
151
|
+
else
|
152
|
+
attributes = attributes_to_hash(@attributes, document)
|
160
153
|
end
|
161
154
|
|
162
155
|
attributes.merge!(attributes_to_hash(@additional_attributes, document)) if @additional_attributes
|
@@ -171,36 +164,34 @@ module MeiliSearch
|
|
171
164
|
attributes = sanitize_attributes(attributes, sanitizer)
|
172
165
|
end
|
173
166
|
|
174
|
-
if @options[:force_utf8_encoding]
|
175
|
-
attributes = encode_attributes(attributes)
|
176
|
-
end
|
167
|
+
attributes = encode_attributes(attributes) if @options[:force_utf8_encoding]
|
177
168
|
|
178
169
|
attributes
|
179
170
|
end
|
180
171
|
|
181
|
-
def sanitize_attributes(
|
182
|
-
case
|
172
|
+
def sanitize_attributes(value, sanitizer)
|
173
|
+
case value
|
183
174
|
when String
|
184
|
-
sanitizer.sanitize(
|
175
|
+
sanitizer.sanitize(value)
|
185
176
|
when Hash
|
186
|
-
|
177
|
+
value.each { |key, val| value[key] = sanitize_attributes(val, sanitizer) }
|
187
178
|
when Array
|
188
|
-
|
179
|
+
value.map { |item| sanitize_attributes(item, sanitizer) }
|
189
180
|
else
|
190
|
-
|
181
|
+
value
|
191
182
|
end
|
192
183
|
end
|
193
184
|
|
194
|
-
def encode_attributes(
|
195
|
-
case
|
185
|
+
def encode_attributes(value)
|
186
|
+
case value
|
196
187
|
when String
|
197
|
-
|
188
|
+
value.force_encoding('utf-8')
|
198
189
|
when Hash
|
199
|
-
|
190
|
+
value.each { |key, val| value[key] = encode_attributes(val) }
|
200
191
|
when Array
|
201
|
-
|
192
|
+
value.map { |x| encode_attributes(x) }
|
202
193
|
else
|
203
|
-
|
194
|
+
value
|
204
195
|
end
|
205
196
|
end
|
206
197
|
|
@@ -212,14 +203,17 @@ module MeiliSearch
|
|
212
203
|
settings = {}
|
213
204
|
OPTIONS.each do |k|
|
214
205
|
v = get_setting(k)
|
215
|
-
settings[k] = v
|
206
|
+
settings[k] = v unless v.nil?
|
216
207
|
end
|
217
208
|
settings
|
218
209
|
end
|
219
210
|
|
220
211
|
def add_index(index_uid, options = {}, &block)
|
221
|
-
raise ArgumentError
|
222
|
-
|
212
|
+
raise ArgumentError, 'No block given' unless block_given?
|
213
|
+
if options[:auto_index] || options[:auto_remove]
|
214
|
+
raise ArgumentError, 'Options auto_index and auto_remove cannot be set on nested indexes'
|
215
|
+
end
|
216
|
+
|
223
217
|
@additional_indexes ||= {}
|
224
218
|
options[:index_uid] = index_uid
|
225
219
|
@additional_indexes[options] = IndexSettings.new(options, &block)
|
@@ -242,27 +236,28 @@ module MeiliSearch
|
|
242
236
|
class SafeIndex
|
243
237
|
def initialize(index_uid, raise_on_failure, options)
|
244
238
|
client = MeiliSearch.client
|
245
|
-
primary_key = options[:primary_key] ||
|
239
|
+
primary_key = options[:primary_key] || MeiliSearch::IndexSettings::DEFAULT_PRIMARY_KEY
|
246
240
|
@index = client.get_or_create_index(index_uid, { primaryKey: primary_key })
|
247
241
|
@raise_on_failure = raise_on_failure.nil? || raise_on_failure
|
248
242
|
end
|
249
243
|
|
250
244
|
::MeiliSearch::Index.instance_methods(false).each do |m|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
end
|
257
|
-
SafeIndex.log_or_throw(m, @raise_on_failure) do
|
258
|
-
@index.send(m, *args, &block)
|
259
|
-
end
|
245
|
+
define_method(m) do |*args, &block|
|
246
|
+
if m == :update_settings
|
247
|
+
args[0].delete(:attributesToHighlight) if args[0][:attributesToHighlight]
|
248
|
+
args[0].delete(:attributesToCrop) if args[0][:attributesToCrop]
|
249
|
+
args[0].delete(:cropLength) if args[0][:cropLength]
|
260
250
|
end
|
251
|
+
SafeIndex.log_or_throw(m, @raise_on_failure) do
|
252
|
+
@index.send(m, *args, &block)
|
253
|
+
end
|
254
|
+
end
|
261
255
|
end
|
262
256
|
|
263
257
|
# special handling of wait_for_pending_update to handle null task_id
|
264
258
|
def wait_for_pending_update(update_id)
|
265
259
|
return if update_id.nil? && !@raise_on_failure # ok
|
260
|
+
|
266
261
|
SafeIndex.log_or_throw(:wait_for_pending_update, @raise_on_failure) do
|
267
262
|
@index.wait_for_pending_update(update_id)
|
268
263
|
end
|
@@ -271,41 +266,37 @@ module MeiliSearch
|
|
271
266
|
# special handling of settings to avoid raising errors on 404
|
272
267
|
def settings(*args)
|
273
268
|
SafeIndex.log_or_throw(:settings, @raise_on_failure) do
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
end
|
269
|
+
@index.settings(*args)
|
270
|
+
rescue ::MeiliSearch::ApiError => e
|
271
|
+
return {} if e.code == 404 # not fatal
|
272
|
+
|
273
|
+
raise e
|
280
274
|
end
|
281
275
|
end
|
282
276
|
|
283
|
-
private
|
284
277
|
def self.log_or_throw(method, raise_on_failure, &block)
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
end
|
278
|
+
yield
|
279
|
+
rescue ::MeiliSearch::ApiError => e
|
280
|
+
raise e if raise_on_failure
|
281
|
+
|
282
|
+
# log the error
|
283
|
+
(Rails.logger || Logger.new($stdout)).error("[meilisearch-rails] #{e.message}")
|
284
|
+
# return something
|
285
|
+
case method.to_s
|
286
|
+
when 'search'
|
287
|
+
# some attributes are required
|
288
|
+
{ 'hits' => [], 'hitsPerPage' => 0, 'page' => 0, 'facetsDistribution' => {}, 'error' => e }
|
289
|
+
else
|
290
|
+
# empty answer
|
291
|
+
{ 'error' => e }
|
300
292
|
end
|
301
293
|
end
|
302
294
|
end
|
303
295
|
|
304
296
|
# these are the class methods added when MeiliSearch is included
|
305
297
|
module ClassMethods
|
306
|
-
|
307
298
|
def self.extended(base)
|
308
|
-
class <<base
|
299
|
+
class << base
|
309
300
|
alias_method :without_auto_index, :ms_without_auto_index unless method_defined? :without_auto_index
|
310
301
|
alias_method :reindex!, :ms_reindex! unless method_defined? :reindex!
|
311
302
|
alias_method :index_documents, :ms_index_documents unless method_defined? :index_documents
|
@@ -324,7 +315,10 @@ module MeiliSearch
|
|
324
315
|
|
325
316
|
def meilisearch(options = {}, &block)
|
326
317
|
self.meilisearch_settings = IndexSettings.new(options, &block)
|
327
|
-
self.meilisearch_options = {
|
318
|
+
self.meilisearch_options = {
|
319
|
+
type: ms_full_const_get(model_name.to_s),
|
320
|
+
per_page: meilisearch_settings.get_setting(:hitsPerPage) || 20, page: 1
|
321
|
+
}.merge(options)
|
328
322
|
|
329
323
|
attr_accessor :formatted
|
330
324
|
|
@@ -338,24 +332,25 @@ module MeiliSearch
|
|
338
332
|
ms_mark_synchronous
|
339
333
|
end
|
340
334
|
end
|
341
|
-
|
342
|
-
after_validation :ms_mark_synchronous
|
335
|
+
elsif respond_to?(:after_validation)
|
336
|
+
after_validation :ms_mark_synchronous
|
343
337
|
end
|
344
338
|
end
|
345
339
|
if options[:enqueue]
|
346
|
-
raise ArgumentError
|
340
|
+
raise ArgumentError, 'Cannot use a enqueue if the `synchronous` option if set' if options[:synchronous]
|
341
|
+
|
347
342
|
proc = if options[:enqueue] == true
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
meilisearch_options[:enqueue] =
|
343
|
+
proc do |record, remove|
|
344
|
+
MSJob.perform_later(record, remove ? 'ms_remove_from_index!' : 'ms_index!')
|
345
|
+
end
|
346
|
+
elsif options[:enqueue].respond_to?(:call)
|
347
|
+
options[:enqueue]
|
348
|
+
elsif options[:enqueue].is_a?(Symbol)
|
349
|
+
proc { |record, remove| send(options[:enqueue], record, remove) }
|
350
|
+
else
|
351
|
+
raise ArgumentError, "Invalid `enqueue` option: #{options[:enqueue]}"
|
352
|
+
end
|
353
|
+
meilisearch_options[:enqueue] = proc do |record, remove|
|
359
354
|
proc.call(record, remove) unless ms_without_auto_index_scope
|
360
355
|
end
|
361
356
|
end
|
@@ -390,7 +385,7 @@ module MeiliSearch
|
|
390
385
|
define_method(:after_save) do |*args|
|
391
386
|
super(*args)
|
392
387
|
copy_after_save.bind(self).call
|
393
|
-
|
388
|
+
db.after_commit do
|
394
389
|
ms_perform_index_tasks
|
395
390
|
end
|
396
391
|
end
|
@@ -417,8 +412,8 @@ module MeiliSearch
|
|
417
412
|
super(*args)
|
418
413
|
end
|
419
414
|
end
|
420
|
-
|
421
|
-
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
|
415
|
+
elsif respond_to?(:after_destroy)
|
416
|
+
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
|
422
417
|
end
|
423
418
|
end
|
424
419
|
end
|
@@ -433,17 +428,19 @@ module MeiliSearch
|
|
433
428
|
end
|
434
429
|
|
435
430
|
def ms_without_auto_index_scope=(value)
|
436
|
-
Thread.current["ms_without_auto_index_scope_for_#{
|
431
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"] = value
|
437
432
|
end
|
438
433
|
|
439
434
|
def ms_without_auto_index_scope
|
440
|
-
Thread.current["ms_without_auto_index_scope_for_#{
|
435
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"]
|
441
436
|
end
|
442
437
|
|
443
438
|
def ms_reindex!(batch_size = MeiliSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
|
444
439
|
return if ms_without_auto_index_scope
|
440
|
+
|
445
441
|
ms_configurations.each do |options, settings|
|
446
442
|
next if ms_indexing_disabled?(options)
|
443
|
+
|
447
444
|
index = ms_ensure_init(options, settings)
|
448
445
|
last_update = nil
|
449
446
|
|
@@ -451,20 +448,18 @@ module MeiliSearch
|
|
451
448
|
if ms_conditional_index?(options)
|
452
449
|
# delete non-indexable documents
|
453
450
|
ids = group.select { |d| !ms_indexable?(d, options) }.map { |d| ms_primary_key_of(d, options) }
|
454
|
-
index.delete_documents(ids.select
|
451
|
+
index.delete_documents(ids.select(&:present?))
|
455
452
|
# select only indexable documents
|
456
453
|
group = group.select { |d| ms_indexable?(d, options) }
|
457
454
|
end
|
458
455
|
documents = group.map do |d|
|
459
456
|
attributes = settings.get_attributes(d)
|
460
|
-
|
461
|
-
|
462
|
-
end
|
463
|
-
attributes.merge ms_pk(options) => ms_primary_key_of(d, options)
|
457
|
+
attributes = attributes.to_hash unless attributes.instance_of?(Hash)
|
458
|
+
attributes.merge ms_pk(options) => ms_primary_key_of(d, options)
|
464
459
|
end
|
465
460
|
last_update= index.add_documents(documents)
|
466
461
|
end
|
467
|
-
index.wait_for_pending_update(last_update[
|
462
|
+
index.wait_for_pending_update(last_update['updateId']) if last_update && (synchronous || options[:synchronous])
|
468
463
|
end
|
469
464
|
nil
|
470
465
|
end
|
@@ -480,37 +475,40 @@ module MeiliSearch
|
|
480
475
|
|
481
476
|
index = SafeIndex.new(ms_index_uid(options), true, options)
|
482
477
|
update = index.update_settings(final_settings)
|
483
|
-
index.wait_for_pending_update(update[
|
478
|
+
index.wait_for_pending_update(update['updateId']) if synchronous
|
484
479
|
end
|
485
480
|
end
|
486
481
|
|
487
482
|
def ms_index_documents(documents, synchronous = false)
|
488
483
|
ms_configurations.each do |options, settings|
|
489
484
|
next if ms_indexing_disabled?(options)
|
485
|
+
|
490
486
|
index = ms_ensure_init(options, settings)
|
491
487
|
update = index.add_documents(documents.map { |d| settings.get_attributes(d).merge ms_pk(options) => ms_primary_key_of(d, options) })
|
492
|
-
index.wait_for_pending_update(update[
|
488
|
+
index.wait_for_pending_update(update['updateId']) if synchronous || options[:synchronous]
|
493
489
|
end
|
494
490
|
end
|
495
491
|
|
496
492
|
def ms_index!(document, synchronous = false)
|
497
493
|
return if ms_without_auto_index_scope
|
494
|
+
|
498
495
|
ms_configurations.each do |options, settings|
|
499
496
|
next if ms_indexing_disabled?(options)
|
497
|
+
|
500
498
|
primary_key = ms_primary_key_of(document, options)
|
501
499
|
index = ms_ensure_init(options, settings)
|
502
500
|
if ms_indexable?(document, options)
|
503
|
-
raise ArgumentError
|
501
|
+
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?
|
502
|
+
|
503
|
+
doc = settings.get_attributes(document)
|
504
|
+
doc = doc.merge ms_pk(options) => primary_key
|
505
|
+
|
504
506
|
if synchronous || options[:synchronous]
|
505
|
-
doc = settings.get_attributes(document)
|
506
|
-
doc = doc.merge ms_pk(options) => primary_key
|
507
507
|
index.add_documents!(doc)
|
508
508
|
else
|
509
|
-
doc = settings.get_attributes(document)
|
510
|
-
doc = doc.merge ms_pk(options) => primary_key
|
511
509
|
index.add_documents(doc)
|
512
510
|
end
|
513
|
-
elsif ms_conditional_index?(options) &&
|
511
|
+
elsif ms_conditional_index?(options) && primary_key.present?
|
514
512
|
# remove non-indexable documents
|
515
513
|
if synchronous || options[:synchronous]
|
516
514
|
index.delete_document!(primary_key)
|
@@ -524,10 +522,13 @@ module MeiliSearch
|
|
524
522
|
|
525
523
|
def ms_remove_from_index!(document, synchronous = false)
|
526
524
|
return if ms_without_auto_index_scope
|
525
|
+
|
527
526
|
primary_key = ms_primary_key_of(document)
|
528
|
-
raise ArgumentError
|
527
|
+
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?
|
528
|
+
|
529
529
|
ms_configurations.each do |options, settings|
|
530
530
|
next if ms_indexing_disabled?(options)
|
531
|
+
|
531
532
|
index = ms_ensure_init(options, settings)
|
532
533
|
if synchronous || options[:synchronous]
|
533
534
|
index.delete_document!(primary_key)
|
@@ -541,6 +542,7 @@ module MeiliSearch
|
|
541
542
|
def ms_clear_index!(synchronous = false)
|
542
543
|
ms_configurations.each do |options, settings|
|
543
544
|
next if ms_indexing_disabled?(options)
|
545
|
+
|
544
546
|
index = ms_ensure_init(options, settings)
|
545
547
|
synchronous || options[:synchronous] ? index.delete_all_documents! : index.delete_all_documents
|
546
548
|
@ms_indexes[settings] = nil
|
@@ -549,24 +551,27 @@ module MeiliSearch
|
|
549
551
|
end
|
550
552
|
|
551
553
|
def ms_raw_search(q, params = {})
|
552
|
-
index_uid = params.delete(:index) ||
|
553
|
-
params.delete('index')
|
554
|
+
index_uid = params.delete(:index) || params.delete('index')
|
554
555
|
|
555
|
-
|
556
|
+
unless meilisearch_settings.get_setting(:attributesToHighlight).nil?
|
556
557
|
params[:attributesToHighlight] = meilisearch_settings.get_setting(:attributesToHighlight)
|
557
558
|
end
|
558
559
|
|
559
|
-
|
560
|
+
unless meilisearch_settings.get_setting(:attributesToCrop).nil?
|
560
561
|
params[:attributesToCrop] = meilisearch_settings.get_setting(:attributesToCrop)
|
561
|
-
|
562
|
+
|
563
|
+
unless meilisearch_settings.get_setting(:cropLength).nil?
|
564
|
+
params[:cropLength] = meilisearch_settings.get_setting(:cropLength)
|
565
|
+
end
|
562
566
|
end
|
567
|
+
|
563
568
|
index = ms_index(index_uid)
|
564
|
-
index.search(q,
|
569
|
+
index.search(q, params.map { |k, v| [k, v] }.to_h)
|
565
570
|
end
|
566
571
|
|
567
572
|
module AdditionalMethods
|
568
573
|
def self.extended(base)
|
569
|
-
class <<base
|
574
|
+
class << base
|
570
575
|
alias_method :raw_answer, :ms_raw_answer unless method_defined? :raw_answer
|
571
576
|
alias_method :facets_distribution, :ms_facets_distribution unless method_defined? :facets_distribution
|
572
577
|
end
|
@@ -581,12 +586,13 @@ module MeiliSearch
|
|
581
586
|
end
|
582
587
|
|
583
588
|
private
|
589
|
+
|
584
590
|
def ms_init_raw_answer(json)
|
585
591
|
@ms_json = json
|
586
592
|
end
|
587
593
|
end
|
588
594
|
|
589
|
-
def ms_search(
|
595
|
+
def ms_search(query, params = {})
|
590
596
|
if MeiliSearch.configuration[:pagination_backend]
|
591
597
|
|
592
598
|
page = params[:page].nil? ? params[:page] : params[:page].to_i
|
@@ -598,28 +604,29 @@ module MeiliSearch
|
|
598
604
|
end
|
599
605
|
|
600
606
|
# Returns raw json hits as follows:
|
601
|
-
# {"hits"=>[{"id"=>"13", "href"=>"apple", "name"=>"iphone"}], "offset"=>0, "limit"=>|| 20, "nbHits"=>1,
|
602
|
-
|
607
|
+
# {"hits"=>[{"id"=>"13", "href"=>"apple", "name"=>"iphone"}], "offset"=>0, "limit"=>|| 20, "nbHits"=>1,
|
608
|
+
# "exhaustiveNbHits"=>false, "processingTimeMs"=>0, "query"=>"iphone"}
|
609
|
+
json = ms_raw_search(query, params)
|
603
610
|
|
604
611
|
# Returns the ids of the hits: 13
|
605
612
|
hit_ids = json['hits'].map { |hit| hit[ms_pk(meilisearch_options).to_s] }
|
606
613
|
|
607
614
|
# condition_key gets the primary key of the document; looks for "id" on the options
|
608
|
-
if defined?(::Mongoid::Document) &&
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
615
|
+
condition_key = if defined?(::Mongoid::Document) && include?(::Mongoid::Document)
|
616
|
+
ms_primary_key_method.in
|
617
|
+
else
|
618
|
+
ms_primary_key_method
|
619
|
+
end
|
613
620
|
|
614
621
|
# meilisearch_options[:type] refers to the Model name (e.g. Product)
|
615
|
-
# results_by_id creates a hash with the primaryKey of the document (id) as the key and
|
616
|
-
# {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil,
|
622
|
+
# results_by_id creates a hash with the primaryKey of the document (id) as the key and doc itself as the value
|
623
|
+
# {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil,
|
624
|
+
# description: "Puts even more features at your fingertips", release_date: nil>}
|
617
625
|
results_by_id = meilisearch_options[:type].where(condition_key => hit_ids).index_by do |hit|
|
618
626
|
ms_primary_key_of(hit)
|
619
627
|
end
|
620
628
|
|
621
629
|
results = json['hits'].map do |hit|
|
622
|
-
|
623
630
|
o = results_by_id[hit[ms_pk(meilisearch_options).to_s].to_s]
|
624
631
|
if o
|
625
632
|
o.formatted = hit['_formatted']
|
@@ -631,7 +638,7 @@ module MeiliSearch
|
|
631
638
|
hits_per_page ||= 20
|
632
639
|
page ||= 1
|
633
640
|
|
634
|
-
res = MeiliSearch::Pagination.create(results, total_hits, meilisearch_options.merge(
|
641
|
+
res = MeiliSearch::Pagination.create(results, total_hits, meilisearch_options.merge(page: page, per_page: hits_per_page))
|
635
642
|
res.extend(AdditionalMethods)
|
636
643
|
res.send(:ms_init_raw_answer, json)
|
637
644
|
res
|
@@ -642,7 +649,7 @@ module MeiliSearch
|
|
642
649
|
ms_configurations.each do |o, s|
|
643
650
|
return ms_ensure_init(o, s) if o[:index_uid].to_s == name.to_s
|
644
651
|
end
|
645
|
-
raise ArgumentError
|
652
|
+
raise ArgumentError, "Invalid index name: #{name}"
|
646
653
|
end
|
647
654
|
ms_ensure_init
|
648
655
|
end
|
@@ -650,17 +657,19 @@ module MeiliSearch
|
|
650
657
|
def ms_index_uid(options = nil)
|
651
658
|
options ||= meilisearch_options
|
652
659
|
name = options[:index_uid] || model_name.to_s.gsub('::', '_')
|
653
|
-
name = "#{name}_#{Rails.env
|
660
|
+
name = "#{name}_#{Rails.env}" if options[:per_environment]
|
654
661
|
name
|
655
662
|
end
|
656
663
|
|
657
664
|
def ms_must_reindex?(document)
|
658
665
|
# use +ms_dirty?+ method if implemented
|
659
|
-
return document.send(:ms_dirty?) if
|
666
|
+
return document.send(:ms_dirty?) if document.respond_to?(:ms_dirty?)
|
667
|
+
|
660
668
|
# Loop over each index to see if a attribute used in records has changed
|
661
669
|
ms_configurations.each do |options, settings|
|
662
670
|
next if ms_indexing_disabled?(options)
|
663
671
|
return true if ms_primary_key_changed?(document, options)
|
672
|
+
|
664
673
|
settings.get_attribute_names(document).each do |k|
|
665
674
|
return true if ms_attribute_changed?(document, k)
|
666
675
|
# return true if !document.respond_to?(changed_method) || document.send(changed_method)
|
@@ -678,14 +687,15 @@ module MeiliSearch
|
|
678
687
|
end
|
679
688
|
end
|
680
689
|
end
|
690
|
+
|
681
691
|
# By default, we don't reindex
|
682
|
-
|
692
|
+
false
|
683
693
|
end
|
684
694
|
|
685
695
|
protected
|
686
696
|
|
687
697
|
def ms_ensure_init(options = nil, settings = nil, index_settings = nil)
|
688
|
-
raise ArgumentError
|
698
|
+
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
689
699
|
|
690
700
|
@ms_indexes ||= {}
|
691
701
|
|
@@ -696,7 +706,7 @@ module MeiliSearch
|
|
696
706
|
|
697
707
|
@ms_indexes[settings] = SafeIndex.new(ms_index_uid(options), meilisearch_options[:raise_on_failure], meilisearch_options)
|
698
708
|
|
699
|
-
current_settings = @ms_indexes[settings].settings(:
|
709
|
+
current_settings = @ms_indexes[settings].settings(getVersion: 1) rescue nil # if the index doesn't exist
|
700
710
|
|
701
711
|
index_settings ||= settings.to_settings
|
702
712
|
index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit]
|
@@ -713,17 +723,18 @@ module MeiliSearch
|
|
713
723
|
private
|
714
724
|
|
715
725
|
def ms_configurations
|
716
|
-
raise ArgumentError
|
726
|
+
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
727
|
+
|
717
728
|
if @configurations.nil?
|
718
729
|
@configurations = {}
|
719
730
|
@configurations[meilisearch_options] = meilisearch_settings
|
720
|
-
meilisearch_settings.additional_indexes.each do |k,v|
|
731
|
+
meilisearch_settings.additional_indexes.each do |k, v|
|
721
732
|
@configurations[k] = v
|
722
733
|
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
734
|
+
next unless v.additional_indexes.any?
|
735
|
+
|
736
|
+
v.additional_indexes.each do |options, index|
|
737
|
+
@configurations[options] = index
|
727
738
|
end
|
728
739
|
end
|
729
740
|
end
|
@@ -750,13 +761,14 @@ module MeiliSearch
|
|
750
761
|
|
751
762
|
def meilisearch_settings_changed?(prev, current)
|
752
763
|
return true if prev.nil?
|
764
|
+
|
753
765
|
current.each do |k, v|
|
754
766
|
prev_v = prev[k.to_s]
|
755
|
-
if v.is_a?(Array)
|
767
|
+
if v.is_a?(Array) && prev_v.is_a?(Array)
|
756
768
|
# compare array of strings, avoiding symbols VS strings comparison
|
757
|
-
return true if v.map
|
758
|
-
|
759
|
-
return true
|
769
|
+
return true if v.map(&:to_s) != prev_v.map(&:to_s)
|
770
|
+
elsif prev_v != v
|
771
|
+
return true
|
760
772
|
end
|
761
773
|
end
|
762
774
|
false
|
@@ -796,11 +808,11 @@ module MeiliSearch
|
|
796
808
|
# All constraints must pass
|
797
809
|
constraint.all? { |inner_constraint| ms_constraint_passes?(document, inner_constraint) }
|
798
810
|
else
|
799
|
-
|
800
|
-
constraint.call(document)
|
801
|
-
else
|
811
|
+
unless constraint.respond_to?(:call)
|
802
812
|
raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
|
803
813
|
end
|
814
|
+
|
815
|
+
constraint.call(document)
|
804
816
|
end
|
805
817
|
end
|
806
818
|
|
@@ -830,7 +842,7 @@ module MeiliSearch
|
|
830
842
|
items = []
|
831
843
|
all.each do |item|
|
832
844
|
items << item
|
833
|
-
if items.length % batch_size
|
845
|
+
if (items.length % batch_size).zero?
|
834
846
|
yield items
|
835
847
|
items = []
|
836
848
|
end
|
@@ -869,7 +881,9 @@ module MeiliSearch
|
|
869
881
|
|
870
882
|
def ms_enqueue_remove_from_index!(synchronous)
|
871
883
|
if meilisearch_options[:enqueue]
|
872
|
-
|
884
|
+
unless self.class.send(:ms_indexing_disabled?, meilisearch_options)
|
885
|
+
meilisearch_options[:enqueue].call(self, true)
|
886
|
+
end
|
873
887
|
else
|
874
888
|
ms_remove_from_index!(synchronous || ms_synchronous?)
|
875
889
|
end
|
@@ -877,18 +891,20 @@ module MeiliSearch
|
|
877
891
|
|
878
892
|
def ms_enqueue_index!(synchronous)
|
879
893
|
if meilisearch_options[:enqueue]
|
880
|
-
|
894
|
+
unless self.class.send(:ms_indexing_disabled?, meilisearch_options)
|
895
|
+
meilisearch_options[:enqueue].call(self, false)
|
896
|
+
end
|
881
897
|
else
|
882
898
|
ms_index!(synchronous)
|
883
899
|
end
|
884
900
|
end
|
885
901
|
|
886
|
-
private
|
887
|
-
|
888
902
|
def ms_synchronous?
|
889
|
-
@ms_synchronous
|
903
|
+
@ms_synchronous
|
890
904
|
end
|
891
905
|
|
906
|
+
private
|
907
|
+
|
892
908
|
def ms_mark_synchronous
|
893
909
|
@ms_synchronous = true
|
894
910
|
end
|
@@ -899,18 +915,19 @@ module MeiliSearch
|
|
899
915
|
|
900
916
|
def ms_mark_must_reindex
|
901
917
|
# ms_must_reindex flag is reset after every commit as part. If we must reindex at any point in
|
902
|
-
# a
|
918
|
+
# a transaction, keep flag set until it is explicitly unset
|
903
919
|
@ms_must_reindex ||=
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
920
|
+
if defined?(::Sequel) && is_a?(Sequel::Model)
|
921
|
+
new? || self.class.ms_must_reindex?(self)
|
922
|
+
else
|
923
|
+
new_record? || self.class.ms_must_reindex?(self)
|
924
|
+
end
|
909
925
|
true
|
910
926
|
end
|
911
927
|
|
912
928
|
def ms_perform_index_tasks
|
913
929
|
return if !@ms_auto_indexing || @ms_must_reindex == false
|
930
|
+
|
914
931
|
ms_enqueue_index!(ms_synchronous?)
|
915
932
|
remove_instance_variable(:@ms_auto_indexing) if instance_variable_defined?(:@ms_auto_indexing)
|
916
933
|
remove_instance_variable(:@ms_synchronous) if instance_variable_defined?(:@ms_synchronous)
|