meilisearch-rails 0.2.2 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +25 -17
- data/README.md +21 -27
- 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 +10 -14
- data/lib/meilisearch/version.rb +3 -1
- data/lib/meilisearch-rails.rb +208 -202
- data/meilisearch-rails.gemspec +25 -35
- metadata +4 -5
- 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.to_h { |name, value| [name.to_s, value.call(document)] }
|
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,70 +236,68 @@ 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] ||
|
246
|
-
|
239
|
+
primary_key = options[:primary_key] || MeiliSearch::IndexSettings::DEFAULT_PRIMARY_KEY
|
240
|
+
client.create_index(index_uid, { primaryKey: primary_key })
|
241
|
+
@index = client.index(index_uid)
|
247
242
|
@raise_on_failure = raise_on_failure.nil? || raise_on_failure
|
248
243
|
end
|
249
244
|
|
250
245
|
::MeiliSearch::Index.instance_methods(false).each do |m|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
end
|
246
|
+
define_method(m) do |*args, &block|
|
247
|
+
if m == :update_settings
|
248
|
+
args[0].delete(:attributesToHighlight) if args[0][:attributesToHighlight]
|
249
|
+
args[0].delete(:attributesToCrop) if args[0][:attributesToCrop]
|
250
|
+
args[0].delete(:cropLength) if args[0][:cropLength]
|
251
|
+
end
|
252
|
+
SafeIndex.log_or_throw(m, @raise_on_failure) do
|
253
|
+
@index.send(m, *args, &block)
|
260
254
|
end
|
255
|
+
end
|
261
256
|
end
|
262
257
|
|
263
|
-
# special handling of
|
264
|
-
def
|
265
|
-
return if
|
266
|
-
|
267
|
-
|
258
|
+
# special handling of wait_for_task to handle null task_id
|
259
|
+
def wait_for_task(task_uid)
|
260
|
+
return if task_uid.nil? && !@raise_on_failure # ok
|
261
|
+
|
262
|
+
SafeIndex.log_or_throw(:wait_for_task, @raise_on_failure) do
|
263
|
+
@index.wait_for_task(task_uid)
|
268
264
|
end
|
269
265
|
end
|
270
266
|
|
271
267
|
# special handling of settings to avoid raising errors on 404
|
272
268
|
def settings(*args)
|
273
269
|
SafeIndex.log_or_throw(:settings, @raise_on_failure) do
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
end
|
270
|
+
@index.settings(*args)
|
271
|
+
rescue ::MeiliSearch::ApiError => e
|
272
|
+
return {} if e.code == 404 # not fatal
|
273
|
+
|
274
|
+
raise e
|
280
275
|
end
|
281
276
|
end
|
282
277
|
|
283
|
-
private
|
284
278
|
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
|
279
|
+
yield
|
280
|
+
rescue ::MeiliSearch::ApiError => e
|
281
|
+
raise e if raise_on_failure
|
282
|
+
|
283
|
+
# log the error
|
284
|
+
(Rails.logger || Logger.new($stdout)).error("[meilisearch-rails] #{e.message}")
|
285
|
+
# return something
|
286
|
+
case method.to_s
|
287
|
+
when 'search'
|
288
|
+
# some attributes are required
|
289
|
+
{ 'hits' => [], 'hitsPerPage' => 0, 'page' => 0, 'facetsDistribution' => {}, 'error' => e }
|
290
|
+
else
|
291
|
+
# empty answer
|
292
|
+
{ 'error' => e }
|
300
293
|
end
|
301
294
|
end
|
302
295
|
end
|
303
296
|
|
304
297
|
# these are the class methods added when MeiliSearch is included
|
305
298
|
module ClassMethods
|
306
|
-
|
307
299
|
def self.extended(base)
|
308
|
-
class <<base
|
300
|
+
class << base
|
309
301
|
alias_method :without_auto_index, :ms_without_auto_index unless method_defined? :without_auto_index
|
310
302
|
alias_method :reindex!, :ms_reindex! unless method_defined? :reindex!
|
311
303
|
alias_method :index_documents, :ms_index_documents unless method_defined? :index_documents
|
@@ -324,7 +316,10 @@ module MeiliSearch
|
|
324
316
|
|
325
317
|
def meilisearch(options = {}, &block)
|
326
318
|
self.meilisearch_settings = IndexSettings.new(options, &block)
|
327
|
-
self.meilisearch_options = {
|
319
|
+
self.meilisearch_options = {
|
320
|
+
type: model_name.to_s.constantize,
|
321
|
+
per_page: meilisearch_settings.get_setting(:hitsPerPage) || 20, page: 1
|
322
|
+
}.merge(options)
|
328
323
|
|
329
324
|
attr_accessor :formatted
|
330
325
|
|
@@ -338,24 +333,25 @@ module MeiliSearch
|
|
338
333
|
ms_mark_synchronous
|
339
334
|
end
|
340
335
|
end
|
341
|
-
|
342
|
-
after_validation :ms_mark_synchronous
|
336
|
+
elsif respond_to?(:after_validation)
|
337
|
+
after_validation :ms_mark_synchronous
|
343
338
|
end
|
344
339
|
end
|
345
340
|
if options[:enqueue]
|
346
|
-
raise ArgumentError
|
341
|
+
raise ArgumentError, 'Cannot use a enqueue if the `synchronous` option if set' if options[:synchronous]
|
342
|
+
|
347
343
|
proc = if options[:enqueue] == true
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
meilisearch_options[:enqueue] =
|
344
|
+
proc do |record, remove|
|
345
|
+
MSJob.perform_later(record, remove ? 'ms_remove_from_index!' : 'ms_index!')
|
346
|
+
end
|
347
|
+
elsif options[:enqueue].respond_to?(:call)
|
348
|
+
options[:enqueue]
|
349
|
+
elsif options[:enqueue].is_a?(Symbol)
|
350
|
+
proc { |record, remove| send(options[:enqueue], record, remove) }
|
351
|
+
else
|
352
|
+
raise ArgumentError, "Invalid `enqueue` option: #{options[:enqueue]}"
|
353
|
+
end
|
354
|
+
meilisearch_options[:enqueue] = proc do |record, remove|
|
359
355
|
proc.call(record, remove) unless ms_without_auto_index_scope
|
360
356
|
end
|
361
357
|
end
|
@@ -390,7 +386,7 @@ module MeiliSearch
|
|
390
386
|
define_method(:after_save) do |*args|
|
391
387
|
super(*args)
|
392
388
|
copy_after_save.bind(self).call
|
393
|
-
|
389
|
+
db.after_commit do
|
394
390
|
ms_perform_index_tasks
|
395
391
|
end
|
396
392
|
end
|
@@ -417,8 +413,8 @@ module MeiliSearch
|
|
417
413
|
super(*args)
|
418
414
|
end
|
419
415
|
end
|
420
|
-
|
421
|
-
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
|
416
|
+
elsif respond_to?(:after_destroy)
|
417
|
+
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
|
422
418
|
end
|
423
419
|
end
|
424
420
|
end
|
@@ -433,38 +429,38 @@ module MeiliSearch
|
|
433
429
|
end
|
434
430
|
|
435
431
|
def ms_without_auto_index_scope=(value)
|
436
|
-
Thread.current["ms_without_auto_index_scope_for_#{
|
432
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"] = value
|
437
433
|
end
|
438
434
|
|
439
435
|
def ms_without_auto_index_scope
|
440
|
-
Thread.current["ms_without_auto_index_scope_for_#{
|
436
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"]
|
441
437
|
end
|
442
438
|
|
443
439
|
def ms_reindex!(batch_size = MeiliSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
|
444
440
|
return if ms_without_auto_index_scope
|
441
|
+
|
445
442
|
ms_configurations.each do |options, settings|
|
446
443
|
next if ms_indexing_disabled?(options)
|
444
|
+
|
447
445
|
index = ms_ensure_init(options, settings)
|
448
|
-
|
446
|
+
last_task = nil
|
449
447
|
|
450
448
|
ms_find_in_batches(batch_size) do |group|
|
451
449
|
if ms_conditional_index?(options)
|
452
450
|
# delete non-indexable documents
|
453
451
|
ids = group.select { |d| !ms_indexable?(d, options) }.map { |d| ms_primary_key_of(d, options) }
|
454
|
-
index.delete_documents(ids.select
|
452
|
+
index.delete_documents(ids.select(&:present?))
|
455
453
|
# select only indexable documents
|
456
454
|
group = group.select { |d| ms_indexable?(d, options) }
|
457
455
|
end
|
458
456
|
documents = group.map do |d|
|
459
457
|
attributes = settings.get_attributes(d)
|
460
|
-
|
461
|
-
|
462
|
-
end
|
463
|
-
attributes.merge ms_pk(options) => ms_primary_key_of(d, options)
|
458
|
+
attributes = attributes.to_hash unless attributes.instance_of?(Hash)
|
459
|
+
attributes.merge ms_pk(options) => ms_primary_key_of(d, options)
|
464
460
|
end
|
465
|
-
|
461
|
+
last_task = index.add_documents(documents)
|
466
462
|
end
|
467
|
-
index.
|
463
|
+
index.wait_for_task(last_task['uid']) if last_task && (synchronous || options[:synchronous])
|
468
464
|
end
|
469
465
|
nil
|
470
466
|
end
|
@@ -479,38 +475,41 @@ module MeiliSearch
|
|
479
475
|
end
|
480
476
|
|
481
477
|
index = SafeIndex.new(ms_index_uid(options), true, options)
|
482
|
-
|
483
|
-
index.
|
478
|
+
task = index.update_settings(final_settings)
|
479
|
+
index.wait_for_task(task['uid']) if synchronous
|
484
480
|
end
|
485
481
|
end
|
486
482
|
|
487
483
|
def ms_index_documents(documents, synchronous = false)
|
488
484
|
ms_configurations.each do |options, settings|
|
489
485
|
next if ms_indexing_disabled?(options)
|
486
|
+
|
490
487
|
index = ms_ensure_init(options, settings)
|
491
|
-
|
492
|
-
index.
|
488
|
+
task = index.add_documents(documents.map { |d| settings.get_attributes(d).merge ms_pk(options) => ms_primary_key_of(d, options) })
|
489
|
+
index.wait_for_task(task['uid']) if synchronous || options[:synchronous]
|
493
490
|
end
|
494
491
|
end
|
495
492
|
|
496
493
|
def ms_index!(document, synchronous = false)
|
497
494
|
return if ms_without_auto_index_scope
|
495
|
+
|
498
496
|
ms_configurations.each do |options, settings|
|
499
497
|
next if ms_indexing_disabled?(options)
|
498
|
+
|
500
499
|
primary_key = ms_primary_key_of(document, options)
|
501
500
|
index = ms_ensure_init(options, settings)
|
502
501
|
if ms_indexable?(document, options)
|
503
|
-
raise ArgumentError
|
502
|
+
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?
|
503
|
+
|
504
|
+
doc = settings.get_attributes(document)
|
505
|
+
doc = doc.merge ms_pk(options) => primary_key
|
506
|
+
|
504
507
|
if synchronous || options[:synchronous]
|
505
|
-
doc = settings.get_attributes(document)
|
506
|
-
doc = doc.merge ms_pk(options) => primary_key
|
507
508
|
index.add_documents!(doc)
|
508
509
|
else
|
509
|
-
doc = settings.get_attributes(document)
|
510
|
-
doc = doc.merge ms_pk(options) => primary_key
|
511
510
|
index.add_documents(doc)
|
512
511
|
end
|
513
|
-
elsif ms_conditional_index?(options) &&
|
512
|
+
elsif ms_conditional_index?(options) && primary_key.present?
|
514
513
|
# remove non-indexable documents
|
515
514
|
if synchronous || options[:synchronous]
|
516
515
|
index.delete_document!(primary_key)
|
@@ -524,10 +523,13 @@ module MeiliSearch
|
|
524
523
|
|
525
524
|
def ms_remove_from_index!(document, synchronous = false)
|
526
525
|
return if ms_without_auto_index_scope
|
526
|
+
|
527
527
|
primary_key = ms_primary_key_of(document)
|
528
|
-
raise ArgumentError
|
528
|
+
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?
|
529
|
+
|
529
530
|
ms_configurations.each do |options, settings|
|
530
531
|
next if ms_indexing_disabled?(options)
|
532
|
+
|
531
533
|
index = ms_ensure_init(options, settings)
|
532
534
|
if synchronous || options[:synchronous]
|
533
535
|
index.delete_document!(primary_key)
|
@@ -541,6 +543,7 @@ module MeiliSearch
|
|
541
543
|
def ms_clear_index!(synchronous = false)
|
542
544
|
ms_configurations.each do |options, settings|
|
543
545
|
next if ms_indexing_disabled?(options)
|
546
|
+
|
544
547
|
index = ms_ensure_init(options, settings)
|
545
548
|
synchronous || options[:synchronous] ? index.delete_all_documents! : index.delete_all_documents
|
546
549
|
@ms_indexes[settings] = nil
|
@@ -549,24 +552,27 @@ module MeiliSearch
|
|
549
552
|
end
|
550
553
|
|
551
554
|
def ms_raw_search(q, params = {})
|
552
|
-
index_uid = params.delete(:index) ||
|
553
|
-
params.delete('index')
|
555
|
+
index_uid = params.delete(:index) || params.delete('index')
|
554
556
|
|
555
|
-
|
557
|
+
unless meilisearch_settings.get_setting(:attributesToHighlight).nil?
|
556
558
|
params[:attributesToHighlight] = meilisearch_settings.get_setting(:attributesToHighlight)
|
557
559
|
end
|
558
560
|
|
559
|
-
|
561
|
+
unless meilisearch_settings.get_setting(:attributesToCrop).nil?
|
560
562
|
params[:attributesToCrop] = meilisearch_settings.get_setting(:attributesToCrop)
|
561
|
-
|
563
|
+
|
564
|
+
unless meilisearch_settings.get_setting(:cropLength).nil?
|
565
|
+
params[:cropLength] = meilisearch_settings.get_setting(:cropLength)
|
566
|
+
end
|
562
567
|
end
|
568
|
+
|
563
569
|
index = ms_index(index_uid)
|
564
|
-
index.search(q,
|
570
|
+
index.search(q, params.to_h { |k, v| [k, v] })
|
565
571
|
end
|
566
572
|
|
567
573
|
module AdditionalMethods
|
568
574
|
def self.extended(base)
|
569
|
-
class <<base
|
575
|
+
class << base
|
570
576
|
alias_method :raw_answer, :ms_raw_answer unless method_defined? :raw_answer
|
571
577
|
alias_method :facets_distribution, :ms_facets_distribution unless method_defined? :facets_distribution
|
572
578
|
end
|
@@ -581,12 +587,13 @@ module MeiliSearch
|
|
581
587
|
end
|
582
588
|
|
583
589
|
private
|
590
|
+
|
584
591
|
def ms_init_raw_answer(json)
|
585
592
|
@ms_json = json
|
586
593
|
end
|
587
594
|
end
|
588
595
|
|
589
|
-
def ms_search(
|
596
|
+
def ms_search(query, params = {})
|
590
597
|
if MeiliSearch.configuration[:pagination_backend]
|
591
598
|
|
592
599
|
page = params[:page].nil? ? params[:page] : params[:page].to_i
|
@@ -598,28 +605,29 @@ module MeiliSearch
|
|
598
605
|
end
|
599
606
|
|
600
607
|
# Returns raw json hits as follows:
|
601
|
-
# {"hits"=>[{"id"=>"13", "href"=>"apple", "name"=>"iphone"}], "offset"=>0, "limit"=>|| 20, "nbHits"=>1,
|
602
|
-
|
608
|
+
# {"hits"=>[{"id"=>"13", "href"=>"apple", "name"=>"iphone"}], "offset"=>0, "limit"=>|| 20, "nbHits"=>1,
|
609
|
+
# "exhaustiveNbHits"=>false, "processingTimeMs"=>0, "query"=>"iphone"}
|
610
|
+
json = ms_raw_search(query, params)
|
603
611
|
|
604
612
|
# Returns the ids of the hits: 13
|
605
613
|
hit_ids = json['hits'].map { |hit| hit[ms_pk(meilisearch_options).to_s] }
|
606
614
|
|
607
615
|
# condition_key gets the primary key of the document; looks for "id" on the options
|
608
|
-
if defined?(::Mongoid::Document) &&
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
616
|
+
condition_key = if defined?(::Mongoid::Document) && include?(::Mongoid::Document)
|
617
|
+
ms_primary_key_method.in
|
618
|
+
else
|
619
|
+
ms_primary_key_method
|
620
|
+
end
|
613
621
|
|
614
622
|
# 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,
|
623
|
+
# results_by_id creates a hash with the primaryKey of the document (id) as the key and doc itself as the value
|
624
|
+
# {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil,
|
625
|
+
# description: "Puts even more features at your fingertips", release_date: nil>}
|
617
626
|
results_by_id = meilisearch_options[:type].where(condition_key => hit_ids).index_by do |hit|
|
618
627
|
ms_primary_key_of(hit)
|
619
628
|
end
|
620
629
|
|
621
630
|
results = json['hits'].map do |hit|
|
622
|
-
|
623
631
|
o = results_by_id[hit[ms_pk(meilisearch_options).to_s].to_s]
|
624
632
|
if o
|
625
633
|
o.formatted = hit['_formatted']
|
@@ -631,7 +639,7 @@ module MeiliSearch
|
|
631
639
|
hits_per_page ||= 20
|
632
640
|
page ||= 1
|
633
641
|
|
634
|
-
res = MeiliSearch::Pagination.create(results, total_hits, meilisearch_options.merge(
|
642
|
+
res = MeiliSearch::Pagination.create(results, total_hits, meilisearch_options.merge(page: page, per_page: hits_per_page))
|
635
643
|
res.extend(AdditionalMethods)
|
636
644
|
res.send(:ms_init_raw_answer, json)
|
637
645
|
res
|
@@ -642,7 +650,7 @@ module MeiliSearch
|
|
642
650
|
ms_configurations.each do |o, s|
|
643
651
|
return ms_ensure_init(o, s) if o[:index_uid].to_s == name.to_s
|
644
652
|
end
|
645
|
-
raise ArgumentError
|
653
|
+
raise ArgumentError, "Invalid index name: #{name}"
|
646
654
|
end
|
647
655
|
ms_ensure_init
|
648
656
|
end
|
@@ -650,17 +658,19 @@ module MeiliSearch
|
|
650
658
|
def ms_index_uid(options = nil)
|
651
659
|
options ||= meilisearch_options
|
652
660
|
name = options[:index_uid] || model_name.to_s.gsub('::', '_')
|
653
|
-
name = "#{name}_#{Rails.env
|
661
|
+
name = "#{name}_#{Rails.env}" if options[:per_environment]
|
654
662
|
name
|
655
663
|
end
|
656
664
|
|
657
665
|
def ms_must_reindex?(document)
|
658
666
|
# use +ms_dirty?+ method if implemented
|
659
|
-
return document.send(:ms_dirty?) if
|
667
|
+
return document.send(:ms_dirty?) if document.respond_to?(:ms_dirty?)
|
668
|
+
|
660
669
|
# Loop over each index to see if a attribute used in records has changed
|
661
670
|
ms_configurations.each do |options, settings|
|
662
671
|
next if ms_indexing_disabled?(options)
|
663
672
|
return true if ms_primary_key_changed?(document, options)
|
673
|
+
|
664
674
|
settings.get_attribute_names(document).each do |k|
|
665
675
|
return true if ms_attribute_changed?(document, k)
|
666
676
|
# return true if !document.respond_to?(changed_method) || document.send(changed_method)
|
@@ -678,14 +688,15 @@ module MeiliSearch
|
|
678
688
|
end
|
679
689
|
end
|
680
690
|
end
|
691
|
+
|
681
692
|
# By default, we don't reindex
|
682
|
-
|
693
|
+
false
|
683
694
|
end
|
684
695
|
|
685
696
|
protected
|
686
697
|
|
687
698
|
def ms_ensure_init(options = nil, settings = nil, index_settings = nil)
|
688
|
-
raise ArgumentError
|
699
|
+
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
689
700
|
|
690
701
|
@ms_indexes ||= {}
|
691
702
|
|
@@ -696,7 +707,7 @@ module MeiliSearch
|
|
696
707
|
|
697
708
|
@ms_indexes[settings] = SafeIndex.new(ms_index_uid(options), meilisearch_options[:raise_on_failure], meilisearch_options)
|
698
709
|
|
699
|
-
current_settings = @ms_indexes[settings].settings(:
|
710
|
+
current_settings = @ms_indexes[settings].settings(getVersion: 1) rescue nil # if the index doesn't exist
|
700
711
|
|
701
712
|
index_settings ||= settings.to_settings
|
702
713
|
index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit]
|
@@ -713,17 +724,18 @@ module MeiliSearch
|
|
713
724
|
private
|
714
725
|
|
715
726
|
def ms_configurations
|
716
|
-
raise ArgumentError
|
727
|
+
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
728
|
+
|
717
729
|
if @configurations.nil?
|
718
730
|
@configurations = {}
|
719
731
|
@configurations[meilisearch_options] = meilisearch_settings
|
720
|
-
meilisearch_settings.additional_indexes.each do |k,v|
|
732
|
+
meilisearch_settings.additional_indexes.each do |k, v|
|
721
733
|
@configurations[k] = v
|
722
734
|
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
735
|
+
next unless v.additional_indexes.any?
|
736
|
+
|
737
|
+
v.additional_indexes.each do |options, index|
|
738
|
+
@configurations[options] = index
|
727
739
|
end
|
728
740
|
end
|
729
741
|
end
|
@@ -750,30 +762,19 @@ module MeiliSearch
|
|
750
762
|
|
751
763
|
def meilisearch_settings_changed?(prev, current)
|
752
764
|
return true if prev.nil?
|
765
|
+
|
753
766
|
current.each do |k, v|
|
754
767
|
prev_v = prev[k.to_s]
|
755
|
-
if v.is_a?(Array)
|
768
|
+
if v.is_a?(Array) && prev_v.is_a?(Array)
|
756
769
|
# compare array of strings, avoiding symbols VS strings comparison
|
757
|
-
return true if v.map
|
758
|
-
|
759
|
-
return true
|
770
|
+
return true if v.map(&:to_s) != prev_v.map(&:to_s)
|
771
|
+
elsif prev_v != v
|
772
|
+
return true
|
760
773
|
end
|
761
774
|
end
|
762
775
|
false
|
763
776
|
end
|
764
777
|
|
765
|
-
def ms_full_const_get(name)
|
766
|
-
list = name.split('::')
|
767
|
-
list.shift if list.first.blank?
|
768
|
-
obj = self
|
769
|
-
list.each do |x|
|
770
|
-
# This is required because const_get tries to look for constants in the
|
771
|
-
# ancestor chain, but we only want constants that are HERE
|
772
|
-
obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
|
773
|
-
end
|
774
|
-
obj
|
775
|
-
end
|
776
|
-
|
777
778
|
def ms_conditional_index?(options = nil)
|
778
779
|
options ||= meilisearch_options
|
779
780
|
options[:if].present? || options[:unless].present?
|
@@ -796,11 +797,11 @@ module MeiliSearch
|
|
796
797
|
# All constraints must pass
|
797
798
|
constraint.all? { |inner_constraint| ms_constraint_passes?(document, inner_constraint) }
|
798
799
|
else
|
799
|
-
|
800
|
-
constraint.call(document)
|
801
|
-
else
|
800
|
+
unless constraint.respond_to?(:call)
|
802
801
|
raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
|
803
802
|
end
|
803
|
+
|
804
|
+
constraint.call(document)
|
804
805
|
end
|
805
806
|
end
|
806
807
|
|
@@ -830,7 +831,7 @@ module MeiliSearch
|
|
830
831
|
items = []
|
831
832
|
all.each do |item|
|
832
833
|
items << item
|
833
|
-
if items.length % batch_size
|
834
|
+
if (items.length % batch_size).zero?
|
834
835
|
yield items
|
835
836
|
items = []
|
836
837
|
end
|
@@ -869,7 +870,9 @@ module MeiliSearch
|
|
869
870
|
|
870
871
|
def ms_enqueue_remove_from_index!(synchronous)
|
871
872
|
if meilisearch_options[:enqueue]
|
872
|
-
|
873
|
+
unless self.class.send(:ms_indexing_disabled?, meilisearch_options)
|
874
|
+
meilisearch_options[:enqueue].call(self, true)
|
875
|
+
end
|
873
876
|
else
|
874
877
|
ms_remove_from_index!(synchronous || ms_synchronous?)
|
875
878
|
end
|
@@ -877,18 +880,20 @@ module MeiliSearch
|
|
877
880
|
|
878
881
|
def ms_enqueue_index!(synchronous)
|
879
882
|
if meilisearch_options[:enqueue]
|
880
|
-
|
883
|
+
unless self.class.send(:ms_indexing_disabled?, meilisearch_options)
|
884
|
+
meilisearch_options[:enqueue].call(self, false)
|
885
|
+
end
|
881
886
|
else
|
882
887
|
ms_index!(synchronous)
|
883
888
|
end
|
884
889
|
end
|
885
890
|
|
886
|
-
private
|
887
|
-
|
888
891
|
def ms_synchronous?
|
889
|
-
@ms_synchronous
|
892
|
+
@ms_synchronous
|
890
893
|
end
|
891
894
|
|
895
|
+
private
|
896
|
+
|
892
897
|
def ms_mark_synchronous
|
893
898
|
@ms_synchronous = true
|
894
899
|
end
|
@@ -899,18 +904,19 @@ module MeiliSearch
|
|
899
904
|
|
900
905
|
def ms_mark_must_reindex
|
901
906
|
# ms_must_reindex flag is reset after every commit as part. If we must reindex at any point in
|
902
|
-
# a
|
907
|
+
# a transaction, keep flag set until it is explicitly unset
|
903
908
|
@ms_must_reindex ||=
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
+
if defined?(::Sequel) && is_a?(Sequel::Model)
|
910
|
+
new? || self.class.ms_must_reindex?(self)
|
911
|
+
else
|
912
|
+
new_record? || self.class.ms_must_reindex?(self)
|
913
|
+
end
|
909
914
|
true
|
910
915
|
end
|
911
916
|
|
912
917
|
def ms_perform_index_tasks
|
913
918
|
return if !@ms_auto_indexing || @ms_must_reindex == false
|
919
|
+
|
914
920
|
ms_enqueue_index!(ms_synchronous?)
|
915
921
|
remove_instance_variable(:@ms_auto_indexing) if instance_variable_defined?(:@ms_auto_indexing)
|
916
922
|
remove_instance_variable(:@ms_synchronous) if instance_variable_defined?(:@ms_synchronous)
|