meilisearch-rails 0.2.3 → 0.5.0

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