meilisearch-rails 0.2.2 → 0.4.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/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)
|