meilisearch-rails 0.2.3 → 0.5.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 +25 -17
- data/LICENSE +1 -1
- data/README.md +86 -80
- data/Rakefile +5 -4
- data/lib/meilisearch/rails/configuration.rb +23 -0
- data/lib/meilisearch/rails/errors.rb +14 -0
- data/lib/meilisearch/rails/ms_job.rb +11 -0
- data/lib/meilisearch/rails/pagination/kaminari.rb +48 -0
- data/lib/meilisearch/rails/pagination/will_paginate.rb +22 -0
- data/lib/meilisearch/rails/pagination.rb +20 -0
- data/lib/meilisearch/rails/railtie.rb +14 -0
- data/lib/meilisearch/rails/tasks/meilisearch.rake +22 -0
- data/lib/meilisearch/rails/utilities.rb +46 -0
- data/lib/meilisearch/rails/version.rb +7 -0
- data/lib/meilisearch-rails.rb +674 -671
- data/meilisearch-rails.gemspec +27 -37
- metadata +15 -16
- data/lib/meilisearch/configuration.rb +0 -19
- data/lib/meilisearch/ms_job.rb +0 -9
- data/lib/meilisearch/pagination/kaminari.rb +0 -44
- data/lib/meilisearch/pagination/will_paginate.rb +0 -19
- data/lib/meilisearch/pagination.rb +0 -19
- data/lib/meilisearch/railtie.rb +0 -11
- data/lib/meilisearch/tasks/meilisearch.rake +0 -19
- data/lib/meilisearch/utilities.rb +0 -49
- data/lib/meilisearch/version.rb +0 -3
- data/spec/spec_helper.rb +0 -52
- data/spec/utilities_spec.rb +0 -36
data/lib/meilisearch-rails.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'meilisearch'
|
2
2
|
|
3
|
-
require 'meilisearch/version'
|
4
|
-
require 'meilisearch/utilities'
|
3
|
+
require 'meilisearch/rails/version'
|
4
|
+
require 'meilisearch/rails/utilities'
|
5
|
+
require 'meilisearch/rails/errors'
|
5
6
|
|
6
7
|
if defined? Rails
|
7
8
|
begin
|
8
|
-
require 'meilisearch/railtie'
|
9
|
+
require 'meilisearch/rails/railtie'
|
9
10
|
rescue LoadError
|
10
11
|
end
|
11
12
|
end
|
@@ -19,237 +20,227 @@ end
|
|
19
20
|
require 'logger'
|
20
21
|
|
21
22
|
module MeiliSearch
|
23
|
+
module Rails
|
24
|
+
autoload :Configuration, 'meilisearch/rails/configuration'
|
25
|
+
extend Configuration
|
22
26
|
|
23
|
-
|
24
|
-
class BadConfiguration < StandardError; end
|
25
|
-
class NoBlockGiven < StandardError; end
|
27
|
+
autoload :Pagination, 'meilisearch/rails/pagination'
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
+
class << self
|
30
|
+
attr_reader :included_in
|
29
31
|
|
30
|
-
|
32
|
+
def included(klass)
|
33
|
+
@included_in ||= []
|
34
|
+
@included_in << klass
|
35
|
+
@included_in.uniq!
|
31
36
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@included_in ||= []
|
37
|
-
@included_in << klass
|
38
|
-
@included_in.uniq!
|
39
|
-
|
40
|
-
klass.class_eval do
|
41
|
-
extend ClassMethods
|
42
|
-
include InstanceMethods
|
37
|
+
klass.class_eval do
|
38
|
+
extend ClassMethods
|
39
|
+
include InstanceMethods
|
40
|
+
end
|
43
41
|
end
|
44
42
|
end
|
45
43
|
|
46
|
-
|
44
|
+
class IndexSettings
|
45
|
+
DEFAULT_BATCH_SIZE = 1000
|
47
46
|
|
48
|
-
|
49
|
-
DEFAULT_BATCH_SIZE = 1000
|
47
|
+
DEFAULT_PRIMARY_KEY = 'id'.freeze
|
50
48
|
|
51
|
-
|
49
|
+
# Meilisearch settings
|
50
|
+
OPTIONS = %i[
|
51
|
+
searchableAttributes
|
52
|
+
filterableAttributes
|
53
|
+
sortableAttributes
|
54
|
+
displayedAttributes
|
55
|
+
distinctAttribute
|
56
|
+
synonyms
|
57
|
+
stopWords
|
58
|
+
rankingRules
|
59
|
+
attributesToHighlight
|
60
|
+
attributesToCrop
|
61
|
+
cropLength
|
62
|
+
].freeze
|
52
63
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
:displayedAttributes,
|
58
|
-
:distinctAttribute,
|
59
|
-
:synonyms,
|
60
|
-
:stopWords,
|
61
|
-
:rankingRules,
|
62
|
-
:attributesToHighlight,
|
63
|
-
:attributesToCrop,
|
64
|
-
:cropLength,
|
65
|
-
]
|
64
|
+
OPTIONS.each do |option|
|
65
|
+
define_method option do |value|
|
66
|
+
instance_variable_set("@#{option}", value)
|
67
|
+
end
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
instance_variable_set("@#{option}", value)
|
69
|
+
underscored_name = option.to_s.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
70
|
+
alias_method underscored_name, option if underscored_name != option
|
70
71
|
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
def initialize(options, &block)
|
74
|
+
@options = options
|
75
|
+
instance_exec(&block) if block_given?
|
76
|
+
end
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
def use_serializer(serializer)
|
79
|
+
@serializer = serializer
|
80
|
+
# instance_variable_set("@serializer", serializer)
|
81
|
+
end
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
-
# instance_variable_set("@serializer", serializer)
|
84
|
-
end
|
83
|
+
def attribute(*names, &block)
|
84
|
+
raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1)
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
@attributes[name.to_s] = block_given? ? Proc.new { |d| d.instance_eval(&block) } : Proc.new { |d| d.send(name) }
|
86
|
+
@attributes ||= {}
|
87
|
+
names.flatten.each do |name|
|
88
|
+
@attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) }
|
89
|
+
end
|
91
90
|
end
|
92
|
-
|
93
|
-
alias :attributes :attribute
|
91
|
+
alias attributes attribute
|
94
92
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
93
|
+
def add_attribute(*names, &block)
|
94
|
+
raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1)
|
95
|
+
|
96
|
+
@additional_attributes ||= {}
|
97
|
+
names.each do |name|
|
98
|
+
@additional_attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) }
|
99
|
+
end
|
100
100
|
end
|
101
|
-
|
102
|
-
alias :add_attributes :add_attribute
|
101
|
+
alias add_attributes add_attribute
|
103
102
|
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
def mongoid?(document)
|
104
|
+
defined?(::Mongoid::Document) && document.class.include?(::Mongoid::Document)
|
105
|
+
end
|
107
106
|
|
108
|
-
|
109
|
-
|
110
|
-
|
107
|
+
def sequel?(document)
|
108
|
+
defined?(::Sequel) && document.class < ::Sequel::Model
|
109
|
+
end
|
111
110
|
|
112
|
-
|
113
|
-
|
114
|
-
|
111
|
+
def active_record?(document)
|
112
|
+
!mongoid?(document) && !sequel?(document)
|
113
|
+
end
|
115
114
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
document.attributes
|
120
|
-
elsif is_sequel?(document)
|
121
|
-
document.to_hash
|
122
|
-
else
|
123
|
-
document.class.unscoped do
|
115
|
+
def get_default_attributes(document)
|
116
|
+
if mongoid?(document)
|
117
|
+
# work-around mongoid 2.4's unscoped method, not accepting a block
|
124
118
|
document.attributes
|
119
|
+
elsif sequel?(document)
|
120
|
+
document.to_hash
|
121
|
+
else
|
122
|
+
document.class.unscoped do
|
123
|
+
document.attributes
|
124
|
+
end
|
125
125
|
end
|
126
126
|
end
|
127
|
-
end
|
128
127
|
|
129
|
-
|
130
|
-
|
131
|
-
|
128
|
+
def get_attribute_names(document)
|
129
|
+
get_attributes(document).keys
|
130
|
+
end
|
132
131
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
132
|
+
def attributes_to_hash(attributes, document)
|
133
|
+
if attributes
|
134
|
+
attributes.to_h { |name, value| [name.to_s, value.call(document)] }
|
135
|
+
else
|
136
|
+
{}
|
137
|
+
end
|
138
138
|
end
|
139
|
-
end
|
140
139
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
if @attributes.nil? || @attributes.length == 0
|
148
|
-
# no `attribute ...` have been configured, use the default attributes of the model
|
140
|
+
def get_attributes(document)
|
141
|
+
# If a serializer is set, we ignore attributes
|
142
|
+
# everything should be done via the serializer
|
143
|
+
if !@serializer.nil?
|
144
|
+
attributes = @serializer.new(document).attributes
|
145
|
+
elsif @attributes.blank?
|
149
146
|
attributes = get_default_attributes(document)
|
150
|
-
|
147
|
+
# no `attribute ...` have been configured, use the default attributes of the model
|
148
|
+
elsif active_record?(document)
|
151
149
|
# 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
|
150
|
+
document.class.unscoped do
|
157
151
|
attributes = attributes_to_hash(@attributes, document)
|
158
152
|
end
|
153
|
+
else
|
154
|
+
attributes = attributes_to_hash(@attributes, document)
|
159
155
|
end
|
160
|
-
end
|
161
156
|
|
162
|
-
|
157
|
+
attributes.merge!(attributes_to_hash(@additional_attributes, document)) if @additional_attributes
|
163
158
|
|
164
|
-
|
165
|
-
|
166
|
-
::HTML::FullSanitizer.new
|
167
|
-
rescue NameError
|
168
|
-
# from rails 4.2
|
169
|
-
::Rails::Html::FullSanitizer.new
|
159
|
+
if @options[:sanitize]
|
160
|
+
attributes = sanitize_attributes(attributes)
|
170
161
|
end
|
171
|
-
|
162
|
+
|
163
|
+
attributes = encode_attributes(attributes) if @options[:force_utf8_encoding]
|
164
|
+
|
165
|
+
attributes
|
172
166
|
end
|
173
167
|
|
174
|
-
|
175
|
-
|
168
|
+
def sanitize_attributes(value)
|
169
|
+
case value
|
170
|
+
when String
|
171
|
+
ActionView::Base.full_sanitizer.sanitize(value)
|
172
|
+
when Hash
|
173
|
+
value.each { |key, val| value[key] = sanitize_attributes(val) }
|
174
|
+
when Array
|
175
|
+
value.map { |item| sanitize_attributes(item) }
|
176
|
+
else
|
177
|
+
value
|
178
|
+
end
|
176
179
|
end
|
177
180
|
|
178
|
-
|
179
|
-
|
181
|
+
def encode_attributes(value)
|
182
|
+
case value
|
183
|
+
when String
|
184
|
+
value.force_encoding('utf-8')
|
185
|
+
when Hash
|
186
|
+
value.each { |key, val| value[key] = encode_attributes(val) }
|
187
|
+
when Array
|
188
|
+
value.map { |x| encode_attributes(x) }
|
189
|
+
else
|
190
|
+
value
|
191
|
+
end
|
192
|
+
end
|
180
193
|
|
181
|
-
|
182
|
-
|
183
|
-
when String
|
184
|
-
sanitizer.sanitize(v)
|
185
|
-
when Hash
|
186
|
-
v.each { |key, value| v[key] = sanitize_attributes(value, sanitizer) }
|
187
|
-
when Array
|
188
|
-
v.map { |x| sanitize_attributes(x, sanitizer) }
|
189
|
-
else
|
190
|
-
v
|
194
|
+
def get_setting(name)
|
195
|
+
instance_variable_get("@#{name}")
|
191
196
|
end
|
192
|
-
end
|
193
197
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
v.map { |x| encode_attributes(x) }
|
202
|
-
else
|
203
|
-
v
|
198
|
+
def to_settings
|
199
|
+
settings = {}
|
200
|
+
OPTIONS.each do |k|
|
201
|
+
v = get_setting(k)
|
202
|
+
settings[k] = v unless v.nil?
|
203
|
+
end
|
204
|
+
settings
|
204
205
|
end
|
205
|
-
end
|
206
206
|
|
207
|
-
|
208
|
-
|
209
|
-
|
207
|
+
def add_index(index_uid, options = {}, &block)
|
208
|
+
raise ArgumentError, 'No block given' unless block_given?
|
209
|
+
if options[:auto_index] || options[:auto_remove]
|
210
|
+
raise ArgumentError, 'Options auto_index and auto_remove cannot be set on nested indexes'
|
211
|
+
end
|
210
212
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
v = get_setting(k)
|
215
|
-
settings[k] = v if !v.nil?
|
213
|
+
@additional_indexes ||= {}
|
214
|
+
options[:index_uid] = index_uid
|
215
|
+
@additional_indexes[options] = IndexSettings.new(options, &block)
|
216
216
|
end
|
217
|
-
settings
|
218
|
-
end
|
219
217
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
@additional_indexes ||= {}
|
224
|
-
options[:index_uid] = index_uid
|
225
|
-
@additional_indexes[options] = IndexSettings.new(options, &block)
|
218
|
+
def additional_indexes
|
219
|
+
@additional_indexes || {}
|
220
|
+
end
|
226
221
|
end
|
227
222
|
|
228
|
-
|
229
|
-
|
223
|
+
# Default queueing system
|
224
|
+
if defined?(::ActiveJob::Base)
|
225
|
+
# lazy load the ActiveJob class to ensure the
|
226
|
+
# queue is initialized before using it
|
227
|
+
autoload :MSJob, 'meilisearch/ms_job'
|
230
228
|
end
|
231
|
-
end
|
232
|
-
|
233
|
-
# Default queueing system
|
234
|
-
if defined?(::ActiveJob::Base)
|
235
|
-
# lazy load the ActiveJob class to ensure the
|
236
|
-
# queue is initialized before using it
|
237
|
-
autoload :MSJob, 'meilisearch/ms_job'
|
238
|
-
end
|
239
229
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
230
|
+
# this class wraps an MeiliSearch::Index document ensuring all raised exceptions
|
231
|
+
# are correctly logged or thrown depending on the `raise_on_failure` option
|
232
|
+
class SafeIndex
|
233
|
+
def initialize(index_uid, raise_on_failure, options)
|
234
|
+
client = MeiliSearch::Rails.client
|
235
|
+
primary_key = options[:primary_key] || MeiliSearch::Rails::IndexSettings::DEFAULT_PRIMARY_KEY
|
236
|
+
client.create_index(index_uid, { primaryKey: primary_key })
|
237
|
+
@index = client.index(index_uid)
|
238
|
+
@raise_on_failure = raise_on_failure.nil? || raise_on_failure
|
239
|
+
end
|
249
240
|
|
250
|
-
|
241
|
+
::MeiliSearch::Index.instance_methods(false).each do |m|
|
251
242
|
define_method(m) do |*args, &block|
|
252
|
-
if
|
243
|
+
if m == :update_settings
|
253
244
|
args[0].delete(:attributesToHighlight) if args[0][:attributesToHighlight]
|
254
245
|
args[0].delete(:attributesToCrop) if args[0][:attributesToCrop]
|
255
246
|
args[0].delete(:cropLength) if args[0][:cropLength]
|
@@ -258,36 +249,35 @@ module MeiliSearch
|
|
258
249
|
@index.send(m, *args, &block)
|
259
250
|
end
|
260
251
|
end
|
261
|
-
|
252
|
+
end
|
253
|
+
|
254
|
+
# special handling of wait_for_task to handle null task_id
|
255
|
+
def wait_for_task(task_uid)
|
256
|
+
return if task_uid.nil? && !@raise_on_failure # ok
|
262
257
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
SafeIndex.log_or_throw(:wait_for_pending_update, @raise_on_failure) do
|
267
|
-
@index.wait_for_pending_update(update_id)
|
258
|
+
SafeIndex.log_or_throw(:wait_for_task, @raise_on_failure) do
|
259
|
+
@index.wait_for_task(task_uid)
|
260
|
+
end
|
268
261
|
end
|
269
|
-
end
|
270
262
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
begin
|
263
|
+
# special handling of settings to avoid raising errors on 404
|
264
|
+
def settings(*args)
|
265
|
+
SafeIndex.log_or_throw(:settings, @raise_on_failure) do
|
275
266
|
@index.settings(*args)
|
276
267
|
rescue ::MeiliSearch::ApiError => e
|
277
268
|
return {} if e.code == 404 # not fatal
|
269
|
+
|
278
270
|
raise e
|
279
271
|
end
|
280
272
|
end
|
281
|
-
end
|
282
273
|
|
283
|
-
|
284
|
-
def self.log_or_throw(method, raise_on_failure, &block)
|
285
|
-
begin
|
274
|
+
def self.log_or_throw(method, raise_on_failure, &block)
|
286
275
|
yield
|
287
276
|
rescue ::MeiliSearch::ApiError => e
|
288
277
|
raise e if raise_on_failure
|
278
|
+
|
289
279
|
# log the error
|
290
|
-
(Rails.logger || Logger.new(
|
280
|
+
(::Rails.logger || Logger.new($stdout)).error("[meilisearch-rails] #{e.message}")
|
291
281
|
# return something
|
292
282
|
case method.to_s
|
293
283
|
when 'search'
|
@@ -299,622 +289,635 @@ module MeiliSearch
|
|
299
289
|
end
|
300
290
|
end
|
301
291
|
end
|
302
|
-
end
|
303
292
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
end
|
293
|
+
# these are the class methods added when MeiliSearch is included
|
294
|
+
module ClassMethods
|
295
|
+
def self.extended(base)
|
296
|
+
class << base
|
297
|
+
alias_method :without_auto_index, :ms_without_auto_index unless method_defined? :without_auto_index
|
298
|
+
alias_method :reindex!, :ms_reindex! unless method_defined? :reindex!
|
299
|
+
alias_method :index_documents, :ms_index_documents unless method_defined? :index_documents
|
300
|
+
alias_method :index!, :ms_index! unless method_defined? :index!
|
301
|
+
alias_method :remove_from_index!, :ms_remove_from_index! unless method_defined? :remove_from_index!
|
302
|
+
alias_method :clear_index!, :ms_clear_index! unless method_defined? :clear_index!
|
303
|
+
alias_method :search, :ms_search unless method_defined? :search
|
304
|
+
alias_method :raw_search, :ms_raw_search unless method_defined? :raw_search
|
305
|
+
alias_method :index, :ms_index unless method_defined? :index
|
306
|
+
alias_method :index_uid, :ms_index_uid unless method_defined? :index_uid
|
307
|
+
alias_method :must_reindex?, :ms_must_reindex? unless method_defined? :must_reindex?
|
308
|
+
end
|
309
|
+
|
310
|
+
base.cattr_accessor :meilisearch_options, :meilisearch_settings
|
311
|
+
end
|
324
312
|
|
325
|
-
|
326
|
-
|
327
|
-
|
313
|
+
def meilisearch(options = {}, &block)
|
314
|
+
self.meilisearch_settings = IndexSettings.new(options, &block)
|
315
|
+
self.meilisearch_options = {
|
316
|
+
type: model_name.to_s.constantize,
|
317
|
+
per_page: meilisearch_settings.get_setting(:hitsPerPage) || 20, page: 1
|
318
|
+
}.merge(options)
|
328
319
|
|
329
|
-
|
320
|
+
attr_accessor :formatted
|
330
321
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
322
|
+
if options[:synchronous] == true
|
323
|
+
if defined?(::Sequel) && self < Sequel::Model
|
324
|
+
class_eval do
|
325
|
+
copy_after_validation = instance_method(:after_validation)
|
326
|
+
define_method(:after_validation) do |*args|
|
327
|
+
super(*args)
|
328
|
+
copy_after_validation.bind(self).call
|
329
|
+
ms_mark_synchronous
|
330
|
+
end
|
339
331
|
end
|
332
|
+
elsif respond_to?(:after_validation)
|
333
|
+
after_validation :ms_mark_synchronous
|
340
334
|
end
|
341
|
-
else
|
342
|
-
after_validation :ms_mark_synchronous if respond_to?(:after_validation)
|
343
335
|
end
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
336
|
+
if options[:enqueue]
|
337
|
+
raise ArgumentError, 'Cannot use a enqueue if the `synchronous` option if set' if options[:synchronous]
|
338
|
+
|
339
|
+
proc = if options[:enqueue] == true
|
340
|
+
proc do |record, remove|
|
341
|
+
MSJob.perform_later(record, remove ? 'ms_remove_from_index!' : 'ms_index!')
|
342
|
+
end
|
343
|
+
elsif options[:enqueue].respond_to?(:call)
|
344
|
+
options[:enqueue]
|
345
|
+
elsif options[:enqueue].is_a?(Symbol)
|
346
|
+
proc { |record, remove| send(options[:enqueue], record, remove) }
|
347
|
+
else
|
348
|
+
raise ArgumentError, "Invalid `enqueue` option: #{options[:enqueue]}"
|
349
|
+
end
|
350
|
+
meilisearch_options[:enqueue] = proc do |record, remove|
|
351
|
+
proc.call(record, remove) unless ms_without_auto_index_scope
|
350
352
|
end
|
351
|
-
elsif options[:enqueue].respond_to?(:call)
|
352
|
-
options[:enqueue]
|
353
|
-
elsif options[:enqueue].is_a?(Symbol)
|
354
|
-
Proc.new { |record, remove| self.send(options[:enqueue], record, remove) }
|
355
|
-
else
|
356
|
-
raise ArgumentError.new("Invalid `enqueue` option: #{options[:enqueue]}")
|
357
353
|
end
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
if defined?(::Sequel) && self < Sequel::Model
|
364
|
-
class_eval do
|
365
|
-
copy_after_validation = instance_method(:after_validation)
|
366
|
-
copy_before_save = instance_method(:before_save)
|
354
|
+
unless options[:auto_index] == false
|
355
|
+
if defined?(::Sequel) && self < Sequel::Model
|
356
|
+
class_eval do
|
357
|
+
copy_after_validation = instance_method(:after_validation)
|
358
|
+
copy_before_save = instance_method(:before_save)
|
367
359
|
|
368
|
-
|
369
|
-
super(*args)
|
370
|
-
copy_after_validation.bind(self).call
|
371
|
-
ms_mark_must_reindex
|
372
|
-
end
|
373
|
-
|
374
|
-
define_method(:before_save) do |*args|
|
375
|
-
copy_before_save.bind(self).call
|
376
|
-
ms_mark_for_auto_indexing
|
377
|
-
super(*args)
|
378
|
-
end
|
379
|
-
|
380
|
-
sequel_version = Gem::Version.new(Sequel.version)
|
381
|
-
if sequel_version >= Gem::Version.new('4.0.0') && sequel_version < Gem::Version.new('5.0.0')
|
382
|
-
copy_after_commit = instance_method(:after_commit)
|
383
|
-
define_method(:after_commit) do |*args|
|
360
|
+
define_method(:after_validation) do |*args|
|
384
361
|
super(*args)
|
385
|
-
|
386
|
-
|
362
|
+
copy_after_validation.bind(self).call
|
363
|
+
ms_mark_must_reindex
|
387
364
|
end
|
388
|
-
|
389
|
-
|
390
|
-
|
365
|
+
|
366
|
+
define_method(:before_save) do |*args|
|
367
|
+
copy_before_save.bind(self).call
|
368
|
+
ms_mark_for_auto_indexing
|
391
369
|
super(*args)
|
392
|
-
|
393
|
-
|
370
|
+
end
|
371
|
+
|
372
|
+
sequel_version = Gem::Version.new(Sequel.version)
|
373
|
+
if sequel_version >= Gem::Version.new('4.0.0') && sequel_version < Gem::Version.new('5.0.0')
|
374
|
+
copy_after_commit = instance_method(:after_commit)
|
375
|
+
define_method(:after_commit) do |*args|
|
376
|
+
super(*args)
|
377
|
+
copy_after_commit.bind(self).call
|
394
378
|
ms_perform_index_tasks
|
395
379
|
end
|
380
|
+
else
|
381
|
+
copy_after_save = instance_method(:after_save)
|
382
|
+
define_method(:after_save) do |*args|
|
383
|
+
super(*args)
|
384
|
+
copy_after_save.bind(self).call
|
385
|
+
db.after_commit do
|
386
|
+
ms_perform_index_tasks
|
387
|
+
end
|
388
|
+
end
|
396
389
|
end
|
397
390
|
end
|
391
|
+
else
|
392
|
+
after_validation :ms_mark_must_reindex if respond_to?(:after_validation)
|
393
|
+
before_save :ms_mark_for_auto_indexing if respond_to?(:before_save)
|
394
|
+
if respond_to?(:after_commit)
|
395
|
+
after_commit :ms_perform_index_tasks
|
396
|
+
elsif respond_to?(:after_save)
|
397
|
+
after_save :ms_perform_index_tasks
|
398
|
+
end
|
398
399
|
end
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
400
|
+
end
|
401
|
+
unless options[:auto_remove] == false
|
402
|
+
if defined?(::Sequel) && self < Sequel::Model
|
403
|
+
class_eval do
|
404
|
+
copy_after_destroy = instance_method(:after_destroy)
|
405
|
+
|
406
|
+
define_method(:after_destroy) do |*args|
|
407
|
+
copy_after_destroy.bind(self).call
|
408
|
+
ms_enqueue_remove_from_index!(ms_synchronous?)
|
409
|
+
super(*args)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
elsif respond_to?(:after_destroy)
|
413
|
+
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
|
406
414
|
end
|
407
415
|
end
|
408
416
|
end
|
409
|
-
unless options[:auto_remove] == false
|
410
|
-
if defined?(::Sequel) && self < Sequel::Model
|
411
|
-
class_eval do
|
412
|
-
copy_after_destroy = instance_method(:after_destroy)
|
413
417
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
else
|
421
|
-
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) } if respond_to?(:after_destroy)
|
418
|
+
def ms_without_auto_index(&block)
|
419
|
+
self.ms_without_auto_index_scope = true
|
420
|
+
begin
|
421
|
+
yield
|
422
|
+
ensure
|
423
|
+
self.ms_without_auto_index_scope = false
|
422
424
|
end
|
423
425
|
end
|
424
|
-
end
|
425
426
|
|
426
|
-
|
427
|
-
|
428
|
-
begin
|
429
|
-
yield
|
430
|
-
ensure
|
431
|
-
self.ms_without_auto_index_scope = false
|
427
|
+
def ms_without_auto_index_scope=(value)
|
428
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"] = value
|
432
429
|
end
|
433
|
-
end
|
434
430
|
|
435
|
-
|
436
|
-
|
437
|
-
|
431
|
+
def ms_without_auto_index_scope
|
432
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"]
|
433
|
+
end
|
438
434
|
|
439
|
-
|
440
|
-
|
441
|
-
end
|
435
|
+
def ms_reindex!(batch_size = MeiliSearch::Rails::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
|
436
|
+
return if ms_without_auto_index_scope
|
442
437
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
attributes = attributes.to_hash
|
438
|
+
ms_configurations.each do |options, settings|
|
439
|
+
next if ms_indexing_disabled?(options)
|
440
|
+
|
441
|
+
index = ms_ensure_init(options, settings)
|
442
|
+
last_task = nil
|
443
|
+
|
444
|
+
ms_find_in_batches(batch_size) do |group|
|
445
|
+
if ms_conditional_index?(options)
|
446
|
+
# delete non-indexable documents
|
447
|
+
ids = group.select { |d| !ms_indexable?(d, options) }.map { |d| ms_primary_key_of(d, options) }
|
448
|
+
index.delete_documents(ids.select(&:present?))
|
449
|
+
# select only indexable documents
|
450
|
+
group = group.select { |d| ms_indexable?(d, options) }
|
451
|
+
end
|
452
|
+
documents = group.map do |d|
|
453
|
+
attributes = settings.get_attributes(d)
|
454
|
+
attributes = attributes.to_hash unless attributes.instance_of?(Hash)
|
455
|
+
attributes.merge ms_pk(options) => ms_primary_key_of(d, options)
|
462
456
|
end
|
463
|
-
|
457
|
+
last_task = index.add_documents(documents)
|
464
458
|
end
|
465
|
-
|
459
|
+
index.wait_for_task(last_task['uid']) if last_task && (synchronous || options[:synchronous])
|
466
460
|
end
|
467
|
-
|
461
|
+
nil
|
468
462
|
end
|
469
|
-
nil
|
470
|
-
end
|
471
463
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
464
|
+
def ms_set_settings(synchronous = false)
|
465
|
+
ms_configurations.each do |options, settings|
|
466
|
+
if options[:primary_settings] && options[:inherit]
|
467
|
+
primary = options[:primary_settings].to_settings
|
468
|
+
final_settings = primary.merge(settings.to_settings)
|
469
|
+
else
|
470
|
+
final_settings = settings.to_settings
|
471
|
+
end
|
480
472
|
|
481
|
-
|
482
|
-
|
483
|
-
|
473
|
+
index = SafeIndex.new(ms_index_uid(options), true, options)
|
474
|
+
task = index.update_settings(final_settings)
|
475
|
+
index.wait_for_task(task['uid']) if synchronous
|
476
|
+
end
|
484
477
|
end
|
485
|
-
end
|
486
478
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
479
|
+
def ms_index_documents(documents, synchronous = false)
|
480
|
+
ms_configurations.each do |options, settings|
|
481
|
+
next if ms_indexing_disabled?(options)
|
482
|
+
|
483
|
+
index = ms_ensure_init(options, settings)
|
484
|
+
task = index.add_documents(documents.map { |d| settings.get_attributes(d).merge ms_pk(options) => ms_primary_key_of(d, options) })
|
485
|
+
index.wait_for_task(task['uid']) if synchronous || options[:synchronous]
|
486
|
+
end
|
493
487
|
end
|
494
|
-
end
|
495
488
|
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
if
|
505
|
-
|
506
|
-
|
507
|
-
index.add_documents!(doc)
|
508
|
-
else
|
489
|
+
def ms_index!(document, synchronous = false)
|
490
|
+
return if ms_without_auto_index_scope
|
491
|
+
|
492
|
+
ms_configurations.each do |options, settings|
|
493
|
+
next if ms_indexing_disabled?(options)
|
494
|
+
|
495
|
+
primary_key = ms_primary_key_of(document, options)
|
496
|
+
index = ms_ensure_init(options, settings)
|
497
|
+
if ms_indexable?(document, options)
|
498
|
+
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?
|
499
|
+
|
509
500
|
doc = settings.get_attributes(document)
|
510
501
|
doc = doc.merge ms_pk(options) => primary_key
|
511
|
-
|
502
|
+
|
503
|
+
if synchronous || options[:synchronous]
|
504
|
+
index.add_documents!(doc)
|
505
|
+
else
|
506
|
+
index.add_documents(doc)
|
507
|
+
end
|
508
|
+
elsif ms_conditional_index?(options) && primary_key.present?
|
509
|
+
# remove non-indexable documents
|
510
|
+
if synchronous || options[:synchronous]
|
511
|
+
index.delete_document!(primary_key)
|
512
|
+
else
|
513
|
+
index.delete_document(primary_key)
|
514
|
+
end
|
512
515
|
end
|
513
|
-
|
514
|
-
|
516
|
+
end
|
517
|
+
nil
|
518
|
+
end
|
519
|
+
|
520
|
+
def ms_remove_from_index!(document, synchronous = false)
|
521
|
+
return if ms_without_auto_index_scope
|
522
|
+
|
523
|
+
primary_key = ms_primary_key_of(document)
|
524
|
+
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?
|
525
|
+
|
526
|
+
ms_configurations.each do |options, settings|
|
527
|
+
next if ms_indexing_disabled?(options)
|
528
|
+
|
529
|
+
index = ms_ensure_init(options, settings)
|
515
530
|
if synchronous || options[:synchronous]
|
516
531
|
index.delete_document!(primary_key)
|
517
532
|
else
|
518
533
|
index.delete_document(primary_key)
|
519
534
|
end
|
520
535
|
end
|
536
|
+
nil
|
521
537
|
end
|
522
|
-
nil
|
523
|
-
end
|
524
538
|
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
raise ArgumentError.new("Cannot index a record without a primary key") if primary_key.blank?
|
529
|
-
ms_configurations.each do |options, settings|
|
530
|
-
next if ms_indexing_disabled?(options)
|
531
|
-
index = ms_ensure_init(options, settings)
|
532
|
-
if synchronous || options[:synchronous]
|
533
|
-
index.delete_document!(primary_key)
|
534
|
-
else
|
535
|
-
index.delete_document(primary_key)
|
536
|
-
end
|
537
|
-
end
|
538
|
-
nil
|
539
|
-
end
|
539
|
+
def ms_clear_index!(synchronous = false)
|
540
|
+
ms_configurations.each do |options, settings|
|
541
|
+
next if ms_indexing_disabled?(options)
|
540
542
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
@ms_indexes[settings] = nil
|
543
|
+
index = ms_ensure_init(options, settings)
|
544
|
+
synchronous || options[:synchronous] ? index.delete_all_documents! : index.delete_all_documents
|
545
|
+
@ms_indexes[settings] = nil
|
546
|
+
end
|
547
|
+
nil
|
547
548
|
end
|
548
|
-
nil
|
549
|
-
end
|
550
549
|
|
551
|
-
|
552
|
-
|
553
|
-
params.delete('index')
|
550
|
+
def ms_raw_search(q, params = {})
|
551
|
+
index_uid = params.delete(:index) || params.delete('index')
|
554
552
|
|
555
|
-
|
556
|
-
|
557
|
-
|
553
|
+
unless meilisearch_settings.get_setting(:attributesToHighlight).nil?
|
554
|
+
params[:attributesToHighlight] = meilisearch_settings.get_setting(:attributesToHighlight)
|
555
|
+
end
|
558
556
|
|
559
|
-
|
560
|
-
|
561
|
-
params[:cropLength] = meilisearch_settings.get_setting(:cropLength) if !meilisearch_settings.get_setting(:cropLength).nil?
|
562
|
-
end
|
563
|
-
index = ms_index(index_uid)
|
564
|
-
index.search(q, Hash[params.map { |k,v| [k, v] }])
|
565
|
-
end
|
557
|
+
unless meilisearch_settings.get_setting(:attributesToCrop).nil?
|
558
|
+
params[:attributesToCrop] = meilisearch_settings.get_setting(:attributesToCrop)
|
566
559
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
alias_method :raw_answer, :ms_raw_answer unless method_defined? :raw_answer
|
571
|
-
alias_method :facets_distribution, :ms_facets_distribution unless method_defined? :facets_distribution
|
560
|
+
unless meilisearch_settings.get_setting(:cropLength).nil?
|
561
|
+
params[:cropLength] = meilisearch_settings.get_setting(:cropLength)
|
562
|
+
end
|
572
563
|
end
|
573
|
-
end
|
574
564
|
|
575
|
-
|
576
|
-
|
565
|
+
index = ms_index(index_uid)
|
566
|
+
index.search(q, params.to_h { |k, v| [k, v] })
|
577
567
|
end
|
578
568
|
|
579
|
-
|
580
|
-
|
581
|
-
|
569
|
+
module AdditionalMethods
|
570
|
+
def self.extended(base)
|
571
|
+
class << base
|
572
|
+
alias_method :raw_answer, :ms_raw_answer unless method_defined? :raw_answer
|
573
|
+
alias_method :facets_distribution, :ms_facets_distribution unless method_defined? :facets_distribution
|
574
|
+
end
|
575
|
+
end
|
582
576
|
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
end
|
587
|
-
end
|
577
|
+
def ms_raw_answer
|
578
|
+
@ms_json
|
579
|
+
end
|
588
580
|
|
589
|
-
|
590
|
-
|
581
|
+
def ms_facets_distribution
|
582
|
+
@ms_json['facetsDistribution']
|
583
|
+
end
|
591
584
|
|
592
|
-
|
593
|
-
hits_per_page = params[:hitsPerPage].nil? ? params[:hitsPerPage] : params[:hitsPerPage].to_i
|
585
|
+
private
|
594
586
|
|
595
|
-
|
596
|
-
|
597
|
-
|
587
|
+
def ms_init_raw_answer(json)
|
588
|
+
@ms_json = json
|
589
|
+
end
|
598
590
|
end
|
599
591
|
|
600
|
-
|
601
|
-
|
602
|
-
json = ms_raw_search(q, params)
|
603
|
-
|
604
|
-
# Returns the ids of the hits: 13
|
605
|
-
hit_ids = json['hits'].map { |hit| hit[ms_pk(meilisearch_options).to_s] }
|
606
|
-
|
607
|
-
# condition_key gets the primary key of the document; looks for "id" on the options
|
608
|
-
if defined?(::Mongoid::Document) && self.include?(::Mongoid::Document)
|
609
|
-
condition_key = ms_primary_key_method.in
|
610
|
-
else
|
611
|
-
condition_key = ms_primary_key_method
|
612
|
-
end
|
592
|
+
def ms_search(query, params = {})
|
593
|
+
if MeiliSearch::Rails.configuration[:pagination_backend]
|
613
594
|
|
614
|
-
|
615
|
-
|
616
|
-
# {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil, description: "Puts even more features at your fingertips", release_date: nil>}
|
617
|
-
results_by_id = meilisearch_options[:type].where(condition_key => hit_ids).index_by do |hit|
|
618
|
-
ms_primary_key_of(hit)
|
619
|
-
end
|
595
|
+
page = params[:page].nil? ? params[:page] : params[:page].to_i
|
596
|
+
hits_per_page = params[:hitsPerPage].nil? ? params[:hitsPerPage] : params[:hitsPerPage].to_i
|
620
597
|
|
621
|
-
|
598
|
+
params.delete(:page)
|
599
|
+
params.delete(:hitsPerPage)
|
600
|
+
params[:limit] = 200
|
601
|
+
end
|
622
602
|
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
603
|
+
# Returns raw json hits as follows:
|
604
|
+
# {"hits"=>[{"id"=>"13", "href"=>"apple", "name"=>"iphone"}], "offset"=>0, "limit"=>|| 20, "nbHits"=>1,
|
605
|
+
# "exhaustiveNbHits"=>false, "processingTimeMs"=>0, "query"=>"iphone"}
|
606
|
+
json = ms_raw_search(query, params)
|
607
|
+
|
608
|
+
# Returns the ids of the hits: 13
|
609
|
+
hit_ids = json['hits'].map { |hit| hit[ms_pk(meilisearch_options).to_s] }
|
610
|
+
|
611
|
+
# condition_key gets the primary key of the document; looks for "id" on the options
|
612
|
+
condition_key = if defined?(::Mongoid::Document) && include?(::Mongoid::Document)
|
613
|
+
ms_primary_key_method.in
|
614
|
+
else
|
615
|
+
ms_primary_key_method
|
616
|
+
end
|
617
|
+
|
618
|
+
# meilisearch_options[:type] refers to the Model name (e.g. Product)
|
619
|
+
# results_by_id creates a hash with the primaryKey of the document (id) as the key and doc itself as the value
|
620
|
+
# {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil,
|
621
|
+
# description: "Puts even more features at your fingertips", release_date: nil>}
|
622
|
+
results_by_id = meilisearch_options[:type].where(condition_key => hit_ids).index_by do |hit|
|
623
|
+
ms_primary_key_of(hit)
|
627
624
|
end
|
628
|
-
end.compact
|
629
625
|
|
630
|
-
|
631
|
-
|
632
|
-
|
626
|
+
results = json['hits'].map do |hit|
|
627
|
+
o = results_by_id[hit[ms_pk(meilisearch_options).to_s].to_s]
|
628
|
+
if o
|
629
|
+
o.formatted = hit['_formatted']
|
630
|
+
o
|
631
|
+
end
|
632
|
+
end.compact
|
633
633
|
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
res
|
638
|
-
end
|
634
|
+
total_hits = json['hits'].length
|
635
|
+
hits_per_page ||= 20
|
636
|
+
page ||= 1
|
639
637
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
638
|
+
res = MeiliSearch::Rails::Pagination.create(results, total_hits, meilisearch_options.merge(page: page, per_page: hits_per_page))
|
639
|
+
res.extend(AdditionalMethods)
|
640
|
+
res.send(:ms_init_raw_answer, json)
|
641
|
+
res
|
642
|
+
end
|
643
|
+
|
644
|
+
def ms_index(name = nil)
|
645
|
+
if name
|
646
|
+
ms_configurations.each do |o, s|
|
647
|
+
return ms_ensure_init(o, s) if o[:index_uid].to_s == name.to_s
|
648
|
+
end
|
649
|
+
raise ArgumentError, "Invalid index name: #{name}"
|
644
650
|
end
|
645
|
-
|
651
|
+
ms_ensure_init
|
646
652
|
end
|
647
|
-
ms_ensure_init
|
648
|
-
end
|
649
653
|
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
654
|
+
def ms_index_uid(options = nil)
|
655
|
+
options ||= meilisearch_options
|
656
|
+
name = options[:index_uid] || model_name.to_s.gsub('::', '_')
|
657
|
+
name = "#{name}_#{::Rails.env}" if options[:per_environment]
|
658
|
+
name
|
659
|
+
end
|
656
660
|
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
661
|
+
def ms_must_reindex?(document)
|
662
|
+
# use +ms_dirty?+ method if implemented
|
663
|
+
return document.send(:ms_dirty?) if document.respond_to?(:ms_dirty?)
|
664
|
+
|
665
|
+
# Loop over each index to see if a attribute used in records has changed
|
666
|
+
ms_configurations.each do |options, settings|
|
667
|
+
next if ms_indexing_disabled?(options)
|
668
|
+
return true if ms_primary_key_changed?(document, options)
|
669
|
+
|
670
|
+
settings.get_attribute_names(document).each do |k|
|
671
|
+
return true if ms_attribute_changed?(document, k)
|
672
|
+
# return true if !document.respond_to?(changed_method) || document.send(changed_method)
|
673
|
+
end
|
674
|
+
[options[:if], options[:unless]].each do |condition|
|
675
|
+
case condition
|
676
|
+
when nil
|
677
|
+
when String, Symbol
|
678
|
+
return true if ms_attribute_changed?(document, condition)
|
679
|
+
else
|
680
|
+
# if the :if, :unless condition is a anything else,
|
681
|
+
# we have no idea whether we should reindex or not
|
682
|
+
# let's always reindex then
|
683
|
+
return true
|
684
|
+
end
|
678
685
|
end
|
679
686
|
end
|
687
|
+
|
688
|
+
# By default, we don't reindex
|
689
|
+
false
|
680
690
|
end
|
681
|
-
# By default, we don't reindex
|
682
|
-
return false
|
683
|
-
end
|
684
691
|
|
685
|
-
|
692
|
+
protected
|
686
693
|
|
687
|
-
|
688
|
-
|
694
|
+
def ms_ensure_init(options = nil, settings = nil, index_settings = nil)
|
695
|
+
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
689
696
|
|
690
|
-
|
697
|
+
@ms_indexes ||= {}
|
691
698
|
|
692
|
-
|
693
|
-
|
699
|
+
options ||= meilisearch_options
|
700
|
+
settings ||= meilisearch_settings
|
694
701
|
|
695
|
-
|
702
|
+
return @ms_indexes[settings] if @ms_indexes[settings]
|
696
703
|
|
697
|
-
|
704
|
+
@ms_indexes[settings] = SafeIndex.new(ms_index_uid(options), meilisearch_options[:raise_on_failure], meilisearch_options)
|
698
705
|
|
699
|
-
|
706
|
+
current_settings = @ms_indexes[settings].settings(getVersion: 1) rescue nil # if the index doesn't exist
|
700
707
|
|
701
|
-
|
702
|
-
|
708
|
+
index_settings ||= settings.to_settings
|
709
|
+
index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit]
|
703
710
|
|
704
|
-
|
711
|
+
options[:check_settings] = true if options[:check_settings].nil?
|
705
712
|
|
706
|
-
|
707
|
-
|
713
|
+
if !ms_indexing_disabled?(options) && options[:check_settings] && meilisearch_settings_changed?(current_settings, index_settings)
|
714
|
+
@ms_indexes[settings].update_settings(index_settings)
|
715
|
+
end
|
716
|
+
|
717
|
+
@ms_indexes[settings]
|
708
718
|
end
|
709
719
|
|
710
|
-
|
711
|
-
end
|
720
|
+
private
|
712
721
|
|
713
|
-
|
722
|
+
def ms_configurations
|
723
|
+
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
714
724
|
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
725
|
+
if @configurations.nil?
|
726
|
+
@configurations = {}
|
727
|
+
@configurations[meilisearch_options] = meilisearch_settings
|
728
|
+
meilisearch_settings.additional_indexes.each do |k, v|
|
729
|
+
@configurations[k] = v
|
730
|
+
|
731
|
+
next unless v.additional_indexes.any?
|
722
732
|
|
723
|
-
if v.additional_indexes.any?
|
724
733
|
v.additional_indexes.each do |options, index|
|
725
734
|
@configurations[options] = index
|
726
735
|
end
|
727
736
|
end
|
728
737
|
end
|
738
|
+
@configurations
|
729
739
|
end
|
730
|
-
@configurations
|
731
|
-
end
|
732
740
|
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
741
|
+
def ms_primary_key_method(options = nil)
|
742
|
+
options ||= meilisearch_options
|
743
|
+
options[:primary_key] || options[:id] || :id
|
744
|
+
end
|
737
745
|
|
738
|
-
|
739
|
-
|
740
|
-
|
746
|
+
def ms_primary_key_of(doc, options = nil)
|
747
|
+
doc.send(ms_primary_key_method(options)).to_s
|
748
|
+
end
|
741
749
|
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
750
|
+
def ms_primary_key_changed?(doc, options = nil)
|
751
|
+
changed = ms_attribute_changed?(doc, ms_primary_key_method(options))
|
752
|
+
changed.nil? ? false : changed
|
753
|
+
end
|
746
754
|
|
747
|
-
|
748
|
-
|
749
|
-
|
755
|
+
def ms_pk(options = nil)
|
756
|
+
options[:primary_key] || MeiliSearch::Rails::IndexSettings::DEFAULT_PRIMARY_KEY
|
757
|
+
end
|
750
758
|
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
759
|
+
def meilisearch_settings_changed?(prev, current)
|
760
|
+
return true if prev.nil?
|
761
|
+
|
762
|
+
current.each do |k, v|
|
763
|
+
prev_v = prev[k.to_s]
|
764
|
+
if v.is_a?(Array) && prev_v.is_a?(Array)
|
765
|
+
# compare array of strings, avoiding symbols VS strings comparison
|
766
|
+
return true if v.map(&:to_s) != prev_v.map(&:to_s)
|
767
|
+
elsif prev_v != v
|
768
|
+
return true
|
769
|
+
end
|
760
770
|
end
|
771
|
+
false
|
761
772
|
end
|
762
|
-
false
|
763
|
-
end
|
764
773
|
|
765
|
-
|
766
|
-
|
767
|
-
|
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)
|
774
|
+
def ms_conditional_index?(options = nil)
|
775
|
+
options ||= meilisearch_options
|
776
|
+
options[:if].present? || options[:unless].present?
|
773
777
|
end
|
774
|
-
obj
|
775
|
-
end
|
776
778
|
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
779
|
+
def ms_indexable?(document, options = nil)
|
780
|
+
options ||= meilisearch_options
|
781
|
+
if_passes = options[:if].blank? || ms_constraint_passes?(document, options[:if])
|
782
|
+
unless_passes = options[:unless].blank? || !ms_constraint_passes?(document, options[:unless])
|
783
|
+
if_passes && unless_passes
|
784
|
+
end
|
781
785
|
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
786
|
+
def ms_constraint_passes?(document, constraint)
|
787
|
+
case constraint
|
788
|
+
when Symbol
|
789
|
+
document.send(constraint)
|
790
|
+
when String
|
791
|
+
document.send(constraint.to_sym)
|
792
|
+
when Enumerable
|
793
|
+
# All constraints must pass
|
794
|
+
constraint.all? { |inner_constraint| ms_constraint_passes?(document, inner_constraint) }
|
795
|
+
else
|
796
|
+
unless constraint.respond_to?(:call)
|
797
|
+
raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
|
798
|
+
end
|
788
799
|
|
789
|
-
def ms_constraint_passes?(document, constraint)
|
790
|
-
case constraint
|
791
|
-
when Symbol
|
792
|
-
document.send(constraint)
|
793
|
-
when String
|
794
|
-
document.send(constraint.to_sym)
|
795
|
-
when Enumerable
|
796
|
-
# All constraints must pass
|
797
|
-
constraint.all? { |inner_constraint| ms_constraint_passes?(document, inner_constraint) }
|
798
|
-
else
|
799
|
-
if constraint.respond_to?(:call) # Proc
|
800
800
|
constraint.call(document)
|
801
|
-
else
|
802
|
-
raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
|
803
801
|
end
|
804
802
|
end
|
805
|
-
end
|
806
803
|
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
804
|
+
def ms_indexing_disabled?(options = nil)
|
805
|
+
options ||= meilisearch_options
|
806
|
+
constraint = options[:disable_indexing] || options['disable_indexing']
|
807
|
+
case constraint
|
808
|
+
when nil
|
809
|
+
return false
|
810
|
+
when true, false
|
811
|
+
return constraint
|
812
|
+
when String, Symbol
|
813
|
+
return send(constraint)
|
814
|
+
else
|
815
|
+
return constraint.call if constraint.respond_to?(:call) # Proc
|
816
|
+
end
|
817
|
+
raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
|
818
|
+
end
|
822
819
|
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
820
|
+
def ms_find_in_batches(batch_size, &block)
|
821
|
+
if (defined?(::ActiveRecord) && ancestors.include?(::ActiveRecord::Base)) || respond_to?(:find_in_batches)
|
822
|
+
find_in_batches(batch_size: batch_size, &block)
|
823
|
+
elsif defined?(::Sequel) && self < Sequel::Model
|
824
|
+
dataset.extension(:pagination).each_page(batch_size, &block)
|
825
|
+
else
|
826
|
+
# don't worry, mongoid has its own underlying cursor/streaming mechanism
|
827
|
+
items = []
|
828
|
+
all.each do |item|
|
829
|
+
items << item
|
830
|
+
if (items.length % batch_size).zero?
|
831
|
+
yield items
|
832
|
+
items = []
|
833
|
+
end
|
836
834
|
end
|
835
|
+
yield items unless items.empty?
|
837
836
|
end
|
838
|
-
yield items unless items.empty?
|
839
837
|
end
|
840
|
-
end
|
841
838
|
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
839
|
+
def ms_attribute_changed?(document, attr_name)
|
840
|
+
if document.respond_to?("will_save_change_to_#{attr_name}?")
|
841
|
+
return document.send("will_save_change_to_#{attr_name}?")
|
842
|
+
end
|
846
843
|
|
847
|
-
|
848
|
-
|
844
|
+
# We don't know if the attribute has changed, so conservatively assume it has
|
845
|
+
true
|
846
|
+
end
|
849
847
|
end
|
850
|
-
end
|
851
848
|
|
852
|
-
|
853
|
-
|
849
|
+
# these are the instance methods included
|
850
|
+
module InstanceMethods
|
854
851
|
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
852
|
+
def self.included(base)
|
853
|
+
base.instance_eval do
|
854
|
+
alias_method :index!, :ms_index! unless method_defined? :index!
|
855
|
+
alias_method :remove_from_index!, :ms_remove_from_index! unless method_defined? :remove_from_index!
|
856
|
+
end
|
859
857
|
end
|
860
|
-
end
|
861
858
|
|
862
|
-
|
863
|
-
|
864
|
-
|
859
|
+
def ms_index!(synchronous = false)
|
860
|
+
self.class.ms_index!(self, synchronous || ms_synchronous?)
|
861
|
+
end
|
865
862
|
|
866
|
-
|
867
|
-
|
868
|
-
|
863
|
+
def ms_remove_from_index!(synchronous = false)
|
864
|
+
self.class.ms_remove_from_index!(self, synchronous || ms_synchronous?)
|
865
|
+
end
|
869
866
|
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
867
|
+
def ms_enqueue_remove_from_index!(synchronous)
|
868
|
+
if meilisearch_options[:enqueue]
|
869
|
+
unless self.class.send(:ms_indexing_disabled?, meilisearch_options)
|
870
|
+
meilisearch_options[:enqueue].call(self, true)
|
871
|
+
end
|
872
|
+
else
|
873
|
+
ms_remove_from_index!(synchronous || ms_synchronous?)
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
def ms_enqueue_index!(synchronous)
|
878
|
+
if meilisearch_options[:enqueue]
|
879
|
+
unless self.class.send(:ms_indexing_disabled?, meilisearch_options)
|
880
|
+
meilisearch_options[:enqueue].call(self, false)
|
881
|
+
end
|
882
|
+
else
|
883
|
+
ms_index!(synchronous)
|
884
|
+
end
|
875
885
|
end
|
876
|
-
end
|
877
886
|
|
878
|
-
|
879
|
-
|
880
|
-
meilisearch_options[:enqueue].call(self, false) unless self.class.send(:ms_indexing_disabled?, meilisearch_options)
|
881
|
-
else
|
882
|
-
ms_index!(synchronous)
|
887
|
+
def ms_synchronous?
|
888
|
+
@ms_synchronous
|
883
889
|
end
|
884
|
-
end
|
885
890
|
|
886
|
-
|
891
|
+
private
|
887
892
|
|
888
|
-
|
889
|
-
|
890
|
-
|
893
|
+
def ms_mark_synchronous
|
894
|
+
@ms_synchronous = true
|
895
|
+
end
|
891
896
|
|
892
|
-
|
893
|
-
|
894
|
-
|
897
|
+
def ms_mark_for_auto_indexing
|
898
|
+
@ms_auto_indexing = true
|
899
|
+
end
|
895
900
|
|
896
|
-
|
897
|
-
|
898
|
-
|
901
|
+
def ms_mark_must_reindex
|
902
|
+
# ms_must_reindex flag is reset after every commit as part. If we must reindex at any point in
|
903
|
+
# a transaction, keep flag set until it is explicitly unset
|
904
|
+
@ms_must_reindex ||=
|
905
|
+
if defined?(::Sequel) && is_a?(Sequel::Model)
|
906
|
+
new? || self.class.ms_must_reindex?(self)
|
907
|
+
else
|
908
|
+
new_record? || self.class.ms_must_reindex?(self)
|
909
|
+
end
|
910
|
+
true
|
911
|
+
end
|
899
912
|
|
900
|
-
|
901
|
-
|
902
|
-
# a stransaction, keep flag set until it is explicitly unset
|
903
|
-
@ms_must_reindex ||=
|
904
|
-
if defined?(::Sequel) && is_a?(Sequel::Model)
|
905
|
-
new? || self.class.ms_must_reindex?(self)
|
906
|
-
else
|
907
|
-
new_record? || self.class.ms_must_reindex?(self)
|
908
|
-
end
|
909
|
-
true
|
910
|
-
end
|
913
|
+
def ms_perform_index_tasks
|
914
|
+
return if !@ms_auto_indexing || @ms_must_reindex == false
|
911
915
|
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
remove_instance_variable(:@ms_must_reindex) if instance_variable_defined?(:@ms_must_reindex)
|
916
|
+
ms_enqueue_index!(ms_synchronous?)
|
917
|
+
remove_instance_variable(:@ms_auto_indexing) if instance_variable_defined?(:@ms_auto_indexing)
|
918
|
+
remove_instance_variable(:@ms_synchronous) if instance_variable_defined?(:@ms_synchronous)
|
919
|
+
remove_instance_variable(:@ms_must_reindex) if instance_variable_defined?(:@ms_must_reindex)
|
920
|
+
end
|
918
921
|
end
|
919
922
|
end
|
920
923
|
end
|