meilisearch-rails 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +23 -16
- data/README.md +40 -33
- data/Rakefile +4 -3
- data/lib/meilisearch/configuration.rb +9 -3
- data/lib/meilisearch/errors.rb +12 -0
- data/lib/meilisearch/pagination/kaminari.rb +13 -7
- 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 +11 -15
- data/lib/meilisearch/version.rb +3 -1
- data/lib/meilisearch-rails.rb +197 -180
- data/meilisearch-rails.gemspec +25 -35
- metadata +6 -7
- data/spec/spec_helper.rb +0 -52
- data/spec/utilities_spec.rb +0 -30
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)
|