meilisearch-rails 0.3.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +3 -2
- data/LICENSE +1 -1
- data/README.md +69 -57
- 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 +690 -704
- data/meilisearch-rails.gemspec +4 -4
- metadata +14 -14
- 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,918 +20,904 @@ 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
|
-
@index = client.get_or_create_index(index_uid, { primaryKey: primary_key })
|
241
|
-
@raise_on_failure = raise_on_failure.nil? || raise_on_failure
|
242
|
-
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
|
243
240
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
250
251
|
end
|
251
|
-
|
252
|
-
|
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)
|
253
260
|
end
|
254
261
|
end
|
255
|
-
end
|
256
262
|
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
260
269
|
|
261
|
-
|
262
|
-
|
270
|
+
raise e
|
271
|
+
end
|
263
272
|
end
|
264
|
-
end
|
265
273
|
|
266
|
-
|
267
|
-
|
268
|
-
SafeIndex.log_or_throw(:settings, @raise_on_failure) do
|
269
|
-
@index.settings(*args)
|
274
|
+
def self.log_or_throw(method, raise_on_failure, &block)
|
275
|
+
yield
|
270
276
|
rescue ::MeiliSearch::ApiError => e
|
271
|
-
|
272
|
-
|
273
|
-
|
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
|
274
290
|
end
|
275
291
|
end
|
276
292
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
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
|
292
311
|
end
|
293
|
-
end
|
294
|
-
end
|
295
312
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
alias_method :index_documents, :ms_index_documents unless method_defined? :index_documents
|
303
|
-
alias_method :index!, :ms_index! unless method_defined? :index!
|
304
|
-
alias_method :remove_from_index!, :ms_remove_from_index! unless method_defined? :remove_from_index!
|
305
|
-
alias_method :clear_index!, :ms_clear_index! unless method_defined? :clear_index!
|
306
|
-
alias_method :search, :ms_search unless method_defined? :search
|
307
|
-
alias_method :raw_search, :ms_raw_search unless method_defined? :raw_search
|
308
|
-
alias_method :index, :ms_index unless method_defined? :index
|
309
|
-
alias_method :index_uid, :ms_index_uid unless method_defined? :index_uid
|
310
|
-
alias_method :must_reindex?, :ms_must_reindex? unless method_defined? :must_reindex?
|
311
|
-
end
|
312
|
-
|
313
|
-
base.cattr_accessor :meilisearch_options, :meilisearch_settings
|
314
|
-
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)
|
315
319
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
class_eval do
|
328
|
-
copy_after_validation = instance_method(:after_validation)
|
329
|
-
define_method(:after_validation) do |*args|
|
330
|
-
super(*args)
|
331
|
-
copy_after_validation.bind(self).call
|
332
|
-
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
|
333
331
|
end
|
332
|
+
elsif respond_to?(:after_validation)
|
333
|
+
after_validation :ms_mark_synchronous
|
334
334
|
end
|
335
|
-
elsif respond_to?(:after_validation)
|
336
|
-
after_validation :ms_mark_synchronous
|
337
335
|
end
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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]}"
|
345
349
|
end
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
end
|
356
|
-
end
|
357
|
-
unless options[:auto_index] == false
|
358
|
-
if defined?(::Sequel) && self < Sequel::Model
|
359
|
-
class_eval do
|
360
|
-
copy_after_validation = instance_method(:after_validation)
|
361
|
-
copy_before_save = instance_method(:before_save)
|
362
|
-
|
363
|
-
define_method(:after_validation) do |*args|
|
364
|
-
super(*args)
|
365
|
-
copy_after_validation.bind(self).call
|
366
|
-
ms_mark_must_reindex
|
367
|
-
end
|
368
|
-
|
369
|
-
define_method(:before_save) do |*args|
|
370
|
-
copy_before_save.bind(self).call
|
371
|
-
ms_mark_for_auto_indexing
|
372
|
-
super(*args)
|
373
|
-
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)
|
374
359
|
|
375
|
-
|
376
|
-
if sequel_version >= Gem::Version.new('4.0.0') && sequel_version < Gem::Version.new('5.0.0')
|
377
|
-
copy_after_commit = instance_method(:after_commit)
|
378
|
-
define_method(:after_commit) do |*args|
|
360
|
+
define_method(:after_validation) do |*args|
|
379
361
|
super(*args)
|
380
|
-
|
381
|
-
|
362
|
+
copy_after_validation.bind(self).call
|
363
|
+
ms_mark_must_reindex
|
382
364
|
end
|
383
|
-
|
384
|
-
|
385
|
-
|
365
|
+
|
366
|
+
define_method(:before_save) do |*args|
|
367
|
+
copy_before_save.bind(self).call
|
368
|
+
ms_mark_for_auto_indexing
|
386
369
|
super(*args)
|
387
|
-
|
388
|
-
|
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
|
389
378
|
ms_perform_index_tasks
|
390
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
|
391
389
|
end
|
392
390
|
end
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
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
|
401
399
|
end
|
402
400
|
end
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
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
|
413
411
|
end
|
412
|
+
elsif respond_to?(:after_destroy)
|
413
|
+
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
|
414
414
|
end
|
415
|
-
elsif respond_to?(:after_destroy)
|
416
|
-
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
|
417
415
|
end
|
418
416
|
end
|
419
|
-
end
|
420
417
|
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
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
|
427
425
|
end
|
428
|
-
end
|
429
426
|
|
430
|
-
|
431
|
-
|
432
|
-
|
427
|
+
def ms_without_auto_index_scope=(value)
|
428
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"] = value
|
429
|
+
end
|
433
430
|
|
434
|
-
|
435
|
-
|
436
|
-
|
431
|
+
def ms_without_auto_index_scope
|
432
|
+
Thread.current["ms_without_auto_index_scope_for_#{model_name}"]
|
433
|
+
end
|
437
434
|
|
438
|
-
|
439
|
-
|
435
|
+
def ms_reindex!(batch_size = MeiliSearch::Rails::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
|
436
|
+
return if ms_without_auto_index_scope
|
440
437
|
|
441
|
-
|
442
|
-
|
438
|
+
ms_configurations.each do |options, settings|
|
439
|
+
next if ms_indexing_disabled?(options)
|
443
440
|
|
444
|
-
|
445
|
-
|
441
|
+
index = ms_ensure_init(options, settings)
|
442
|
+
last_task = nil
|
446
443
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
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)
|
459
458
|
end
|
460
|
-
|
459
|
+
index.wait_for_task(last_task['uid']) if last_task && (synchronous || options[:synchronous])
|
461
460
|
end
|
462
|
-
|
461
|
+
nil
|
463
462
|
end
|
464
|
-
nil
|
465
|
-
end
|
466
463
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
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
|
475
472
|
|
476
|
-
|
477
|
-
|
478
|
-
|
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
|
479
477
|
end
|
480
|
-
end
|
481
478
|
|
482
|
-
|
483
|
-
|
484
|
-
|
479
|
+
def ms_index_documents(documents, synchronous = false)
|
480
|
+
ms_configurations.each do |options, settings|
|
481
|
+
next if ms_indexing_disabled?(options)
|
485
482
|
|
486
|
-
|
487
|
-
|
488
|
-
|
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
|
489
487
|
end
|
490
|
-
end
|
491
488
|
|
492
|
-
|
493
|
-
|
489
|
+
def ms_index!(document, synchronous = false)
|
490
|
+
return if ms_without_auto_index_scope
|
494
491
|
|
495
|
-
|
496
|
-
|
492
|
+
ms_configurations.each do |options, settings|
|
493
|
+
next if ms_indexing_disabled?(options)
|
497
494
|
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
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?
|
502
499
|
|
503
|
-
|
504
|
-
|
500
|
+
doc = settings.get_attributes(document)
|
501
|
+
doc = doc.merge ms_pk(options) => primary_key
|
505
502
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
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
|
517
515
|
end
|
518
516
|
end
|
517
|
+
nil
|
519
518
|
end
|
520
|
-
nil
|
521
|
-
end
|
522
519
|
|
523
|
-
|
524
|
-
|
520
|
+
def ms_remove_from_index!(document, synchronous = false)
|
521
|
+
return if ms_without_auto_index_scope
|
525
522
|
|
526
|
-
|
527
|
-
|
523
|
+
primary_key = ms_primary_key_of(document)
|
524
|
+
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?
|
528
525
|
|
529
|
-
|
530
|
-
|
526
|
+
ms_configurations.each do |options, settings|
|
527
|
+
next if ms_indexing_disabled?(options)
|
531
528
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
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
|
537
535
|
end
|
536
|
+
nil
|
538
537
|
end
|
539
|
-
nil
|
540
|
-
end
|
541
|
-
|
542
|
-
def ms_clear_index!(synchronous = false)
|
543
|
-
ms_configurations.each do |options, settings|
|
544
|
-
next if ms_indexing_disabled?(options)
|
545
|
-
|
546
|
-
index = ms_ensure_init(options, settings)
|
547
|
-
synchronous || options[:synchronous] ? index.delete_all_documents! : index.delete_all_documents
|
548
|
-
@ms_indexes[settings] = nil
|
549
|
-
end
|
550
|
-
nil
|
551
|
-
end
|
552
538
|
|
553
|
-
|
554
|
-
|
539
|
+
def ms_clear_index!(synchronous = false)
|
540
|
+
ms_configurations.each do |options, settings|
|
541
|
+
next if ms_indexing_disabled?(options)
|
555
542
|
|
556
|
-
|
557
|
-
|
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
|
558
548
|
end
|
559
549
|
|
560
|
-
|
561
|
-
|
550
|
+
def ms_raw_search(q, params = {})
|
551
|
+
index_uid = params.delete(:index) || params.delete('index')
|
562
552
|
|
563
|
-
unless meilisearch_settings.get_setting(:
|
564
|
-
params[:
|
553
|
+
unless meilisearch_settings.get_setting(:attributesToHighlight).nil?
|
554
|
+
params[:attributesToHighlight] = meilisearch_settings.get_setting(:attributesToHighlight)
|
565
555
|
end
|
566
|
-
end
|
567
556
|
|
568
|
-
|
569
|
-
|
570
|
-
end
|
557
|
+
unless meilisearch_settings.get_setting(:attributesToCrop).nil?
|
558
|
+
params[:attributesToCrop] = meilisearch_settings.get_setting(:attributesToCrop)
|
571
559
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
alias_method :raw_answer, :ms_raw_answer unless method_defined? :raw_answer
|
576
|
-
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
|
577
563
|
end
|
578
|
-
end
|
579
564
|
|
580
|
-
|
581
|
-
|
565
|
+
index = ms_index(index_uid)
|
566
|
+
index.search(q, params.to_h { |k, v| [k, v] })
|
582
567
|
end
|
583
568
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
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
|
589
576
|
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
end
|
577
|
+
def ms_raw_answer
|
578
|
+
@ms_json
|
579
|
+
end
|
594
580
|
|
595
|
-
|
596
|
-
|
581
|
+
def ms_facets_distribution
|
582
|
+
@ms_json['facetsDistribution']
|
583
|
+
end
|
597
584
|
|
598
|
-
|
599
|
-
hits_per_page = params[:hitsPerPage].nil? ? params[:hitsPerPage] : params[:hitsPerPage].to_i
|
585
|
+
private
|
600
586
|
|
601
|
-
|
602
|
-
|
603
|
-
|
587
|
+
def ms_init_raw_answer(json)
|
588
|
+
@ms_json = json
|
589
|
+
end
|
604
590
|
end
|
605
591
|
|
606
|
-
|
607
|
-
|
608
|
-
# "exhaustiveNbHits"=>false, "processingTimeMs"=>0, "query"=>"iphone"}
|
609
|
-
json = ms_raw_search(query, params)
|
592
|
+
def ms_search(query, params = {})
|
593
|
+
if MeiliSearch::Rails.configuration[:pagination_backend]
|
610
594
|
|
611
|
-
|
612
|
-
|
595
|
+
page = params[:page].nil? ? params[:page] : params[:page].to_i
|
596
|
+
hits_per_page = params[:hitsPerPage].nil? ? params[:hitsPerPage] : params[:hitsPerPage].to_i
|
613
597
|
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
ms_primary_key_method
|
619
|
-
end
|
620
|
-
|
621
|
-
# meilisearch_options[:type] refers to the Model name (e.g. Product)
|
622
|
-
# results_by_id creates a hash with the primaryKey of the document (id) as the key and doc itself as the value
|
623
|
-
# {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil,
|
624
|
-
# description: "Puts even more features at your fingertips", release_date: nil>}
|
625
|
-
results_by_id = meilisearch_options[:type].where(condition_key => hit_ids).index_by do |hit|
|
626
|
-
ms_primary_key_of(hit)
|
627
|
-
end
|
598
|
+
params.delete(:page)
|
599
|
+
params.delete(:hitsPerPage)
|
600
|
+
params[:limit] = 200
|
601
|
+
end
|
628
602
|
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
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)
|
634
624
|
end
|
635
|
-
end.compact
|
636
625
|
|
637
|
-
|
638
|
-
|
639
|
-
|
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
|
640
633
|
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
res
|
645
|
-
end
|
634
|
+
total_hits = json['hits'].length
|
635
|
+
hits_per_page ||= 20
|
636
|
+
page ||= 1
|
646
637
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
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}"
|
651
650
|
end
|
652
|
-
|
651
|
+
ms_ensure_init
|
653
652
|
end
|
654
|
-
ms_ensure_init
|
655
|
-
end
|
656
653
|
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
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
|
663
660
|
|
664
|
-
|
665
|
-
|
666
|
-
|
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?)
|
667
664
|
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
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)
|
672
669
|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
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
|
687
685
|
end
|
688
686
|
end
|
687
|
+
|
688
|
+
# By default, we don't reindex
|
689
|
+
false
|
689
690
|
end
|
690
691
|
|
691
|
-
|
692
|
-
false
|
693
|
-
end
|
692
|
+
protected
|
694
693
|
|
695
|
-
|
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?
|
696
696
|
|
697
|
-
|
698
|
-
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
697
|
+
@ms_indexes ||= {}
|
699
698
|
|
700
|
-
|
699
|
+
options ||= meilisearch_options
|
700
|
+
settings ||= meilisearch_settings
|
701
701
|
|
702
|
-
|
703
|
-
settings ||= meilisearch_settings
|
702
|
+
return @ms_indexes[settings] if @ms_indexes[settings]
|
704
703
|
|
705
|
-
|
704
|
+
@ms_indexes[settings] = SafeIndex.new(ms_index_uid(options), meilisearch_options[:raise_on_failure], meilisearch_options)
|
706
705
|
|
707
|
-
|
706
|
+
current_settings = @ms_indexes[settings].settings(getVersion: 1) rescue nil # if the index doesn't exist
|
708
707
|
|
709
|
-
|
708
|
+
index_settings ||= settings.to_settings
|
709
|
+
index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit]
|
710
710
|
|
711
|
-
|
712
|
-
index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit]
|
711
|
+
options[:check_settings] = true if options[:check_settings].nil?
|
713
712
|
|
714
|
-
|
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
|
715
716
|
|
716
|
-
|
717
|
-
@ms_indexes[settings].update_settings(index_settings)
|
717
|
+
@ms_indexes[settings]
|
718
718
|
end
|
719
719
|
|
720
|
-
|
721
|
-
end
|
722
|
-
|
723
|
-
private
|
720
|
+
private
|
724
721
|
|
725
|
-
|
726
|
-
|
722
|
+
def ms_configurations
|
723
|
+
raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil?
|
727
724
|
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
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
|
733
730
|
|
734
|
-
|
731
|
+
next unless v.additional_indexes.any?
|
735
732
|
|
736
|
-
|
737
|
-
|
733
|
+
v.additional_indexes.each do |options, index|
|
734
|
+
@configurations[options] = index
|
735
|
+
end
|
738
736
|
end
|
739
737
|
end
|
738
|
+
@configurations
|
740
739
|
end
|
741
|
-
@configurations
|
742
|
-
end
|
743
740
|
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
741
|
+
def ms_primary_key_method(options = nil)
|
742
|
+
options ||= meilisearch_options
|
743
|
+
options[:primary_key] || options[:id] || :id
|
744
|
+
end
|
748
745
|
|
749
|
-
|
750
|
-
|
751
|
-
|
746
|
+
def ms_primary_key_of(doc, options = nil)
|
747
|
+
doc.send(ms_primary_key_method(options)).to_s
|
748
|
+
end
|
752
749
|
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
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
|
757
754
|
|
758
|
-
|
759
|
-
|
760
|
-
|
755
|
+
def ms_pk(options = nil)
|
756
|
+
options[:primary_key] || MeiliSearch::Rails::IndexSettings::DEFAULT_PRIMARY_KEY
|
757
|
+
end
|
761
758
|
|
762
|
-
|
763
|
-
|
759
|
+
def meilisearch_settings_changed?(prev, current)
|
760
|
+
return true if prev.nil?
|
764
761
|
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
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
|
772
770
|
end
|
771
|
+
false
|
773
772
|
end
|
774
|
-
false
|
775
|
-
end
|
776
773
|
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
obj = self
|
781
|
-
list.each do |x|
|
782
|
-
# This is required because const_get tries to look for constants in the
|
783
|
-
# ancestor chain, but we only want constants that are HERE
|
784
|
-
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?
|
785
777
|
end
|
786
|
-
obj
|
787
|
-
end
|
788
778
|
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
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
|
793
785
|
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
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
|
800
799
|
|
801
|
-
|
802
|
-
case constraint
|
803
|
-
when Symbol
|
804
|
-
document.send(constraint)
|
805
|
-
when String
|
806
|
-
document.send(constraint.to_sym)
|
807
|
-
when Enumerable
|
808
|
-
# All constraints must pass
|
809
|
-
constraint.all? { |inner_constraint| ms_constraint_passes?(document, inner_constraint) }
|
810
|
-
else
|
811
|
-
unless constraint.respond_to?(:call)
|
812
|
-
raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
|
800
|
+
constraint.call(document)
|
813
801
|
end
|
814
|
-
|
815
|
-
constraint.call(document)
|
816
802
|
end
|
817
|
-
end
|
818
803
|
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
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
|
834
819
|
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
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
|
848
834
|
end
|
835
|
+
yield items unless items.empty?
|
849
836
|
end
|
850
|
-
yield items unless items.empty?
|
851
837
|
end
|
852
|
-
end
|
853
838
|
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
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
|
858
843
|
|
859
|
-
|
860
|
-
|
844
|
+
# We don't know if the attribute has changed, so conservatively assume it has
|
845
|
+
true
|
846
|
+
end
|
861
847
|
end
|
862
|
-
end
|
863
848
|
|
864
|
-
|
865
|
-
|
849
|
+
# these are the instance methods included
|
850
|
+
module InstanceMethods
|
866
851
|
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
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
|
871
857
|
end
|
872
|
-
end
|
873
858
|
|
874
|
-
|
875
|
-
|
876
|
-
|
859
|
+
def ms_index!(synchronous = false)
|
860
|
+
self.class.ms_index!(self, synchronous || ms_synchronous?)
|
861
|
+
end
|
877
862
|
|
878
|
-
|
879
|
-
|
880
|
-
|
863
|
+
def ms_remove_from_index!(synchronous = false)
|
864
|
+
self.class.ms_remove_from_index!(self, synchronous || ms_synchronous?)
|
865
|
+
end
|
881
866
|
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
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?)
|
886
874
|
end
|
887
|
-
else
|
888
|
-
ms_remove_from_index!(synchronous || ms_synchronous?)
|
889
875
|
end
|
890
|
-
end
|
891
876
|
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
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)
|
896
884
|
end
|
897
|
-
else
|
898
|
-
ms_index!(synchronous)
|
899
885
|
end
|
900
|
-
end
|
901
886
|
|
902
|
-
|
903
|
-
|
904
|
-
|
887
|
+
def ms_synchronous?
|
888
|
+
@ms_synchronous
|
889
|
+
end
|
905
890
|
|
906
|
-
|
891
|
+
private
|
907
892
|
|
908
|
-
|
909
|
-
|
910
|
-
|
893
|
+
def ms_mark_synchronous
|
894
|
+
@ms_synchronous = true
|
895
|
+
end
|
911
896
|
|
912
|
-
|
913
|
-
|
914
|
-
|
897
|
+
def ms_mark_for_auto_indexing
|
898
|
+
@ms_auto_indexing = true
|
899
|
+
end
|
915
900
|
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
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
|
927
912
|
|
928
|
-
|
929
|
-
|
913
|
+
def ms_perform_index_tasks
|
914
|
+
return if !@ms_auto_indexing || @ms_must_reindex == false
|
930
915
|
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
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
|
935
921
|
end
|
936
922
|
end
|
937
923
|
end
|