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