algoliasearch-rails 1.25.0 → 3.0.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/CHANGELOG.MD +79 -1
- data/Gemfile +12 -38
- data/Gemfile.lock +157 -149
- data/README.md +13 -41
- data/algoliasearch-rails.gemspec +5 -5
- data/lib/algoliasearch/algolia_job.rb +1 -1
- data/lib/algoliasearch/configuration.rb +33 -4
- data/lib/algoliasearch/pagination/kaminari.rb +1 -1
- data/lib/algoliasearch/pagination/pagy.rb +29 -0
- data/lib/algoliasearch/pagination.rb +1 -0
- data/lib/algoliasearch/utilities.rb +1 -1
- data/lib/algoliasearch/version.rb +1 -1
- data/lib/algoliasearch-rails.rb +185 -207
- data/spec/spec_helper.rb +12 -5
- metadata +8 -14
- data/.travis.yml +0 -62
data/lib/algoliasearch-rails.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'algolia'
|
2
2
|
|
3
3
|
require 'algoliasearch/version'
|
4
4
|
require 'algoliasearch/utilities'
|
@@ -53,9 +53,8 @@ module AlgoliaSearch
|
|
53
53
|
OPTIONS = [
|
54
54
|
# Attributes
|
55
55
|
:searchableAttributes, :attributesForFaceting, :unretrievableAttributes, :attributesToRetrieve,
|
56
|
-
:attributesToIndex, #Legacy name of searchableAttributes
|
57
56
|
# Ranking
|
58
|
-
:ranking, :customRanking, # Replicas are handled via `add_replica`
|
57
|
+
:ranking, :customRanking, :relevancyStrictness, # Replicas are handled via `add_replica`
|
59
58
|
# Faceting
|
60
59
|
:maxValuesPerFacet, :sortFacetValuesBy,
|
61
60
|
# Highlighting / Snippeting
|
@@ -76,7 +75,6 @@ module AlgoliaSearch
|
|
76
75
|
:disablePrefixOnAttributes, :disableExactOnAttributes, :exactOnSingleWordQuery, :alternativesAsExact,
|
77
76
|
# Performance
|
78
77
|
:numericAttributesForFiltering, :allowCompressionOfIntegerArray,
|
79
|
-
:numericAttributesToIndex, # Legacy name of numericAttributesForFiltering
|
80
78
|
# Advanced
|
81
79
|
:attributeForDistinct, :distinct, :replaceSynonymsInHighlight, :minProximity, :responseFields,
|
82
80
|
:maxFacetHits,
|
@@ -97,12 +95,11 @@ module AlgoliaSearch
|
|
97
95
|
|
98
96
|
def use_serializer(serializer)
|
99
97
|
@serializer = serializer
|
100
|
-
# instance_variable_set("@serializer", serializer)
|
101
98
|
end
|
102
99
|
|
103
100
|
def attribute(*names, &block)
|
104
101
|
raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
|
105
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
102
|
+
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
|
106
103
|
@attributes ||= {}
|
107
104
|
names.flatten.each do |name|
|
108
105
|
@attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
|
@@ -112,7 +109,7 @@ module AlgoliaSearch
|
|
112
109
|
|
113
110
|
def add_attribute(*names, &block)
|
114
111
|
raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
|
115
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
112
|
+
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
|
116
113
|
@additional_attributes ||= {}
|
117
114
|
names.each do |name|
|
118
115
|
@additional_attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
|
@@ -125,7 +122,7 @@ module AlgoliaSearch
|
|
125
122
|
end
|
126
123
|
|
127
124
|
def is_sequel?(object)
|
128
|
-
defined?(::Sequel) && object.class < ::Sequel::Model
|
125
|
+
defined?(::Sequel) && defined?(::Sequel::Model) && object.class < ::Sequel::Model
|
129
126
|
end
|
130
127
|
|
131
128
|
def is_active_record?(object)
|
@@ -179,16 +176,7 @@ module AlgoliaSearch
|
|
179
176
|
end
|
180
177
|
|
181
178
|
attributes.merge!(attributes_to_hash(@additional_attributes, object)) if @additional_attributes
|
182
|
-
|
183
|
-
if @options[:sanitize]
|
184
|
-
sanitizer = begin
|
185
|
-
::HTML::FullSanitizer.new
|
186
|
-
rescue NameError
|
187
|
-
# from rails 4.2
|
188
|
-
::Rails::Html::FullSanitizer.new
|
189
|
-
end
|
190
|
-
attributes = sanitize_attributes(attributes, sanitizer)
|
191
|
-
end
|
179
|
+
attributes = sanitize_attributes(attributes, Rails::Html::FullSanitizer.new) if @options[:sanitize]
|
192
180
|
|
193
181
|
if @options[:force_utf8_encoding] && Object.const_defined?(:RUBY_VERSION) && RUBY_VERSION.to_f > 1.8
|
194
182
|
attributes = encode_attributes(attributes)
|
@@ -213,7 +201,7 @@ module AlgoliaSearch
|
|
213
201
|
def encode_attributes(v)
|
214
202
|
case v
|
215
203
|
when String
|
216
|
-
v.force_encoding('utf-8')
|
204
|
+
v.dup.force_encoding('utf-8')
|
217
205
|
when Hash
|
218
206
|
v.each { |key, value| v[key] = encode_attributes(value) }
|
219
207
|
when Array
|
@@ -224,14 +212,14 @@ module AlgoliaSearch
|
|
224
212
|
end
|
225
213
|
|
226
214
|
def geoloc(lat_attr = nil, lng_attr = nil, &block)
|
227
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
215
|
+
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
|
228
216
|
add_attribute :_geoloc do |o|
|
229
217
|
block_given? ? o.instance_eval(&block) : { :lat => o.send(lat_attr).to_f, :lng => o.send(lng_attr).to_f }
|
230
218
|
end
|
231
219
|
end
|
232
220
|
|
233
221
|
def tags(*args, &block)
|
234
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
222
|
+
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
|
235
223
|
add_attribute :_tags do |o|
|
236
224
|
v = block_given? ? o.instance_eval(&block) : args
|
237
225
|
v.is_a?(Array) ? v : [v]
|
@@ -243,50 +231,58 @@ module AlgoliaSearch
|
|
243
231
|
end
|
244
232
|
|
245
233
|
def to_settings
|
234
|
+
settings = to_hash
|
235
|
+
|
236
|
+
# Remove the synonyms setting since those need to be set separately
|
237
|
+
settings.delete(:synonyms)
|
238
|
+
settings.delete("synonyms")
|
239
|
+
|
240
|
+
Algolia::Search::IndexSettings.new(settings)
|
241
|
+
end
|
242
|
+
|
243
|
+
def to_hash
|
246
244
|
settings = {}
|
247
245
|
OPTIONS.each do |k|
|
248
246
|
v = get_setting(k)
|
249
|
-
settings[k] = v if !v.nil?
|
247
|
+
settings[setting_name(k)] = v if !v.nil?
|
250
248
|
end
|
251
|
-
|
252
|
-
|
253
|
-
name = opts[:index_name]
|
254
|
-
name = "#{name}_#{Rails.env.to_s}" if opts[:per_environment]
|
255
|
-
name
|
256
|
-
end
|
257
|
-
settings.delete(:slaves) if settings[:slaves].empty?
|
249
|
+
|
250
|
+
if !@options[:replica]
|
258
251
|
settings[:replicas] = additional_indexes.select { |opts, s| opts[:replica] }.map do |opts, s|
|
259
252
|
name = opts[:index_name]
|
260
253
|
name = "#{name}_#{Rails.env.to_s}" if opts[:per_environment]
|
254
|
+
name = "virtual(#{name})" if opts[:virtual]
|
261
255
|
name
|
262
256
|
end
|
263
257
|
settings.delete(:replicas) if settings[:replicas].empty?
|
264
258
|
end
|
259
|
+
|
265
260
|
settings
|
266
261
|
end
|
267
262
|
|
263
|
+
def setting_name(name)
|
264
|
+
name.to_s.gsub(/::/, '/').
|
265
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
266
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
267
|
+
tr("-", "_").
|
268
|
+
downcase
|
269
|
+
end
|
270
|
+
|
268
271
|
def add_index(index_name, options = {}, &block)
|
269
|
-
raise ArgumentError.new('Cannot specify additional index on a replica index') if @options[:
|
272
|
+
raise ArgumentError.new('Cannot specify additional index on a replica index') if @options[:replica]
|
270
273
|
raise ArgumentError.new('No block given') if !block_given?
|
271
274
|
raise ArgumentError.new('Options auto_index and auto_remove cannot be set on nested indexes') if options[:auto_index] || options[:auto_remove]
|
272
275
|
@additional_indexes ||= {}
|
273
|
-
raise MixedSlavesAndReplicas.new('Cannot mix slaves and replicas in the same configuration (add_slave is deprecated)') if (options[:slave] && @additional_indexes.any? { |opts, _| opts[:replica] }) || (options[:replica] && @additional_indexes.any? { |opts, _| opts[:slave] })
|
274
276
|
options[:index_name] = index_name
|
275
277
|
@additional_indexes[options] = IndexSettings.new(options, &block)
|
276
278
|
end
|
277
279
|
|
278
280
|
def add_replica(index_name, options = {}, &block)
|
279
|
-
raise ArgumentError.new('Cannot specify additional replicas on a replica index') if @options[:
|
281
|
+
raise ArgumentError.new('Cannot specify additional replicas on a replica index') if @options[:replica]
|
280
282
|
raise ArgumentError.new('No block given') if !block_given?
|
281
283
|
add_index(index_name, options.merge({ :replica => true, :primary_settings => self }), &block)
|
282
284
|
end
|
283
285
|
|
284
|
-
def add_slave(index_name, options = {}, &block)
|
285
|
-
raise ArgumentError.new('Cannot specify additional slaves on a slave index') if @options[:slave] || @options[:replica]
|
286
|
-
raise ArgumentError.new('No block given') if !block_given?
|
287
|
-
add_index(index_name, options.merge({ :slave => true, :primary_settings => self }), &block)
|
288
|
-
end
|
289
|
-
|
290
286
|
def additional_indexes
|
291
287
|
@additional_indexes || {}
|
292
288
|
end
|
@@ -300,70 +296,6 @@ module AlgoliaSearch
|
|
300
296
|
autoload :AlgoliaJob, 'algoliasearch/algolia_job'
|
301
297
|
end
|
302
298
|
|
303
|
-
# this class wraps an Algolia::Index object ensuring all raised exceptions
|
304
|
-
# are correctly logged or thrown depending on the `raise_on_failure` option
|
305
|
-
class SafeIndex
|
306
|
-
def initialize(name, raise_on_failure)
|
307
|
-
@index = ::Algolia::Index.new(name)
|
308
|
-
@raise_on_failure = raise_on_failure.nil? || raise_on_failure
|
309
|
-
end
|
310
|
-
|
311
|
-
::Algolia::Index.instance_methods(false).each do |m|
|
312
|
-
define_method(m) do |*args, &block|
|
313
|
-
SafeIndex.log_or_throw(m, @raise_on_failure) do
|
314
|
-
@index.send(m, *args, &block)
|
315
|
-
end
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
# special handling of wait_task to handle null task_id
|
320
|
-
def wait_task(task_id)
|
321
|
-
return if task_id.nil? && !@raise_on_failure # ok
|
322
|
-
SafeIndex.log_or_throw(:wait_task, @raise_on_failure) do
|
323
|
-
@index.wait_task(task_id)
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
# special handling of get_settings to avoid raising errors on 404
|
328
|
-
def get_settings(*args)
|
329
|
-
SafeIndex.log_or_throw(:get_settings, @raise_on_failure) do
|
330
|
-
begin
|
331
|
-
@index.get_settings(*args)
|
332
|
-
rescue Algolia::AlgoliaError => e
|
333
|
-
return {} if e.code == 404 # not fatal
|
334
|
-
raise e
|
335
|
-
end
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
# expose move as well
|
340
|
-
def self.move_index(old_name, new_name)
|
341
|
-
SafeIndex.log_or_throw(:move_index, true) do
|
342
|
-
::Algolia.move_index(old_name, new_name)
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
private
|
347
|
-
def self.log_or_throw(method, raise_on_failure, &block)
|
348
|
-
begin
|
349
|
-
yield
|
350
|
-
rescue Algolia::AlgoliaError => e
|
351
|
-
raise e if raise_on_failure
|
352
|
-
# log the error
|
353
|
-
(Rails.logger || Logger.new(STDOUT)).error("[algoliasearch-rails] #{e.message}")
|
354
|
-
# return something
|
355
|
-
case method.to_s
|
356
|
-
when 'search'
|
357
|
-
# some attributes are required
|
358
|
-
{ 'hits' => [], 'hitsPerPage' => 0, 'page' => 0, 'facets' => {}, 'error' => e }
|
359
|
-
else
|
360
|
-
# empty answer
|
361
|
-
{ 'error' => e }
|
362
|
-
end
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
299
|
# these are the class methods added when AlgoliaSearch is included
|
368
300
|
module ClassMethods
|
369
301
|
|
@@ -380,7 +312,6 @@ module AlgoliaSearch
|
|
380
312
|
alias_method :raw_search, :algolia_raw_search unless method_defined? :raw_search
|
381
313
|
alias_method :search_facet, :algolia_search_facet unless method_defined? :search_facet
|
382
314
|
alias_method :search_for_facet_values, :algolia_search_for_facet_values unless method_defined? :search_for_facet_values
|
383
|
-
alias_method :index, :algolia_index unless method_defined? :index
|
384
315
|
alias_method :index_name, :algolia_index_name unless method_defined? :index_name
|
385
316
|
alias_method :must_reindex?, :algolia_must_reindex? unless method_defined? :must_reindex?
|
386
317
|
end
|
@@ -395,7 +326,7 @@ module AlgoliaSearch
|
|
395
326
|
attr_accessor :highlight_result, :snippet_result
|
396
327
|
|
397
328
|
if options[:synchronous] == true
|
398
|
-
if defined?(::Sequel) && self < Sequel::Model
|
329
|
+
if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
399
330
|
class_eval do
|
400
331
|
copy_after_validation = instance_method(:after_validation)
|
401
332
|
define_method(:after_validation) do |*args|
|
@@ -426,7 +357,7 @@ module AlgoliaSearch
|
|
426
357
|
end
|
427
358
|
end
|
428
359
|
unless options[:auto_index] == false
|
429
|
-
if defined?(::Sequel) && self < Sequel::Model
|
360
|
+
if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
430
361
|
class_eval do
|
431
362
|
copy_after_validation = instance_method(:after_validation)
|
432
363
|
copy_before_save = instance_method(:before_save)
|
@@ -473,7 +404,7 @@ module AlgoliaSearch
|
|
473
404
|
end
|
474
405
|
end
|
475
406
|
unless options[:auto_remove] == false
|
476
|
-
if defined?(::Sequel) && self < Sequel::Model
|
407
|
+
if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
477
408
|
class_eval do
|
478
409
|
copy_after_destroy = instance_method(:after_destroy)
|
479
410
|
|
@@ -510,15 +441,16 @@ module AlgoliaSearch
|
|
510
441
|
return if algolia_without_auto_index_scope
|
511
442
|
algolia_configurations.each do |options, settings|
|
512
443
|
next if algolia_indexing_disabled?(options)
|
513
|
-
|
514
|
-
|
444
|
+
algolia_ensure_init(options, settings)
|
445
|
+
index_name = algolia_index_name(options)
|
446
|
+
next if options[:replica]
|
515
447
|
last_task = nil
|
516
448
|
|
517
449
|
algolia_find_in_batches(batch_size) do |group|
|
518
450
|
if algolia_conditional_index?(options)
|
519
451
|
# delete non-indexable objects
|
520
452
|
ids = group.select { |o| !algolia_indexable?(o, options) }.map { |o| algolia_object_id_of(o, options) }
|
521
|
-
|
453
|
+
AlgoliaSearch.client.delete_objects(index_name, ids.select { |id| !id.blank? })
|
522
454
|
# select only indexable objects
|
523
455
|
group = group.select { |o| algolia_indexable?(o, options) }
|
524
456
|
end
|
@@ -529,9 +461,10 @@ module AlgoliaSearch
|
|
529
461
|
end
|
530
462
|
attributes.merge 'objectID' => algolia_object_id_of(o, options)
|
531
463
|
end
|
532
|
-
|
464
|
+
save_tasks = AlgoliaSearch.client.save_objects(index_name, objects)
|
465
|
+
last_task = save_tasks.present? ? save_tasks.last.task_id : last_task
|
533
466
|
end
|
534
|
-
|
467
|
+
AlgoliaSearch.client.wait_for_task(index_name, last_task) if last_task and (synchronous || options[:synchronous])
|
535
468
|
end
|
536
469
|
nil
|
537
470
|
end
|
@@ -541,44 +474,51 @@ module AlgoliaSearch
|
|
541
474
|
return if algolia_without_auto_index_scope
|
542
475
|
algolia_configurations.each do |options, settings|
|
543
476
|
next if algolia_indexing_disabled?(options)
|
544
|
-
next if options[:
|
477
|
+
next if options[:replica]
|
478
|
+
|
479
|
+
algolia_ensure_init(options, settings)
|
480
|
+
index_name = algolia_index_name(options)
|
545
481
|
|
546
482
|
# fetch the master settings
|
547
|
-
|
548
|
-
|
549
|
-
master_settings.merge!(
|
483
|
+
master_settings = AlgoliaSearch.client.get_settings(index_name).to_hash rescue {} # if master doesn't exist yet
|
484
|
+
master_exists = master_settings != {}
|
485
|
+
master_settings.merge!(settings.to_hash)
|
550
486
|
|
551
487
|
# remove the replicas of the temporary index
|
552
|
-
master_settings.delete :slaves
|
553
|
-
master_settings.delete 'slaves'
|
554
488
|
master_settings.delete :replicas
|
555
489
|
master_settings.delete 'replicas'
|
556
490
|
|
557
491
|
# init temporary index
|
558
|
-
|
559
|
-
tmp_index_name = "#{src_index_name}.tmp"
|
492
|
+
tmp_index_name = "#{index_name}.tmp"
|
560
493
|
tmp_options = options.merge({ :index_name => tmp_index_name })
|
561
494
|
tmp_options.delete(:per_environment) # already included in the temporary index_name
|
562
495
|
tmp_settings = settings.dup
|
563
496
|
|
564
|
-
if options[:check_settings] == false
|
565
|
-
|
566
|
-
|
497
|
+
if options[:check_settings] == false && master_exists
|
498
|
+
task_id = AlgoliaSearch.client.operation_index(
|
499
|
+
index_name,
|
500
|
+
Algolia::Search::OperationIndexParams.new(operation: Algolia::Search::OperationType::COPY, destination: tmp_index_name, scope: %w[settings synonyms rules])
|
501
|
+
).task_id
|
502
|
+
AlgoliaSearch.client.wait_for_task(index_name, task_id)
|
567
503
|
else
|
568
|
-
|
504
|
+
algolia_ensure_init(tmp_options, tmp_settings, master_settings)
|
569
505
|
end
|
570
506
|
|
571
|
-
|
507
|
+
algolia_find_in_batches(batch_size) do |group|
|
572
508
|
if algolia_conditional_index?(options)
|
573
509
|
# select only indexable objects
|
574
510
|
group = group.select { |o| algolia_indexable?(o, tmp_options) }
|
575
511
|
end
|
576
512
|
objects = group.map { |o| tmp_settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o, tmp_options) }
|
577
|
-
|
513
|
+
|
514
|
+
AlgoliaSearch.client.save_objects(tmp_index_name, objects)
|
578
515
|
end
|
579
516
|
|
580
|
-
|
581
|
-
|
517
|
+
task_id = AlgoliaSearch.client.operation_index(
|
518
|
+
tmp_index_name,
|
519
|
+
Algolia::Search::OperationIndexParams.new(operation: "move", destination: index_name)
|
520
|
+
).task_id
|
521
|
+
AlgoliaSearch.client.wait_for_task(index_name, task_id) if synchronous || options[:synchronous]
|
582
522
|
end
|
583
523
|
nil
|
584
524
|
end
|
@@ -586,29 +526,40 @@ module AlgoliaSearch
|
|
586
526
|
def algolia_set_settings(synchronous = false)
|
587
527
|
algolia_configurations.each do |options, settings|
|
588
528
|
if options[:primary_settings] && options[:inherit]
|
589
|
-
primary = options[:primary_settings].to_settings
|
590
|
-
primary.delete :slaves
|
591
|
-
primary.delete 'slaves'
|
529
|
+
primary = options[:primary_settings].to_settings.to_hash
|
592
530
|
primary.delete :replicas
|
593
531
|
primary.delete 'replicas'
|
594
|
-
final_settings = primary.merge(settings.to_settings)
|
532
|
+
final_settings = primary.merge(settings.to_settings.to_hash)
|
595
533
|
else
|
596
|
-
final_settings = settings.to_settings
|
534
|
+
final_settings = settings.to_settings.to_hash
|
535
|
+
end
|
536
|
+
|
537
|
+
s = final_settings.map do |k, v|
|
538
|
+
[settings.setting_name(k), v]
|
539
|
+
end.to_h
|
540
|
+
|
541
|
+
synonyms = s.delete("synonyms") || s.delete(:synonyms)
|
542
|
+
unless synonyms.nil? || synonyms.empty?
|
543
|
+
resp = AlgoliaSearch.client.save_synonyms(index_name,synonyms.map {|s| Algolia::Search::SynonymHit.new({object_id: s.join("-"), synonyms: s, type: "synonym"}) } )
|
544
|
+
AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if synchronous || options[:synchronous]
|
597
545
|
end
|
598
546
|
|
599
|
-
|
600
|
-
|
601
|
-
index.wait_task(task["taskID"]) if synchronous
|
547
|
+
resp = AlgoliaSearch.client.set_settings(index_name, Algolia::Search::IndexSettings.new(s))
|
548
|
+
AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if synchronous || options[:synchronous]
|
602
549
|
end
|
603
550
|
end
|
604
551
|
|
605
552
|
def algolia_index_objects(objects, synchronous = false)
|
606
553
|
algolia_configurations.each do |options, settings|
|
607
554
|
next if algolia_indexing_disabled?(options)
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
555
|
+
algolia_ensure_init(options, settings)
|
556
|
+
index_name = algolia_index_name(options)
|
557
|
+
|
558
|
+
next if options[:replica]
|
559
|
+
tasks = AlgoliaSearch.client.save_objects(index_name, objects.map { |o| settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o, options) })
|
560
|
+
tasks.each do |task|
|
561
|
+
AlgoliaSearch.client.wait_for_task(index_name, task.task_id) if synchronous || options[:synchronous]
|
562
|
+
end
|
612
563
|
end
|
613
564
|
end
|
614
565
|
|
@@ -616,22 +567,23 @@ module AlgoliaSearch
|
|
616
567
|
return if algolia_without_auto_index_scope
|
617
568
|
algolia_configurations.each do |options, settings|
|
618
569
|
next if algolia_indexing_disabled?(options)
|
570
|
+
|
619
571
|
object_id = algolia_object_id_of(object, options)
|
620
|
-
|
621
|
-
|
572
|
+
index_name = algolia_index_name(options)
|
573
|
+
algolia_ensure_init(options, settings)
|
574
|
+
next if options[:replica]
|
575
|
+
|
622
576
|
if algolia_indexable?(object, options)
|
623
577
|
raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
|
578
|
+
resp = AlgoliaSearch.client.save_object(index_name, settings.get_attributes(object).merge({ 'objectID' => algolia_object_id_of(object, options) }))
|
624
579
|
if synchronous || options[:synchronous]
|
625
|
-
|
626
|
-
else
|
627
|
-
index.add_object(settings.get_attributes(object), object_id)
|
580
|
+
AlgoliaSearch.client.wait_for_task(index_name, resp.task_id)
|
628
581
|
end
|
629
582
|
elsif algolia_conditional_index?(options) && !object_id.blank?
|
630
583
|
# remove non-indexable objects
|
584
|
+
resp = AlgoliaSearch.client.delete_object(index_name, object_id)
|
631
585
|
if synchronous || options[:synchronous]
|
632
|
-
|
633
|
-
else
|
634
|
-
index.delete_object(object_id)
|
586
|
+
AlgoliaSearch.client.wait_for_task(index_name, resp.task_id)
|
635
587
|
end
|
636
588
|
end
|
637
589
|
end
|
@@ -644,12 +596,14 @@ module AlgoliaSearch
|
|
644
596
|
raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
|
645
597
|
algolia_configurations.each do |options, settings|
|
646
598
|
next if algolia_indexing_disabled?(options)
|
647
|
-
|
648
|
-
|
599
|
+
algolia_ensure_init(options, settings)
|
600
|
+
index_name = algolia_index_name(options)
|
601
|
+
|
602
|
+
next if options[:replica]
|
603
|
+
|
604
|
+
resp = AlgoliaSearch.client.delete_object(index_name, object_id)
|
649
605
|
if synchronous || options[:synchronous]
|
650
|
-
|
651
|
-
else
|
652
|
-
index.delete_object(object_id)
|
606
|
+
AlgoliaSearch.client.wait_for_task(index_name, resp.task_id)
|
653
607
|
end
|
654
608
|
end
|
655
609
|
nil
|
@@ -657,24 +611,38 @@ module AlgoliaSearch
|
|
657
611
|
|
658
612
|
def algolia_clear_index!(synchronous = false)
|
659
613
|
algolia_configurations.each do |options, settings|
|
660
|
-
next if algolia_indexing_disabled?(options)
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
614
|
+
next if algolia_indexing_disabled?(options) || options[:replica]
|
615
|
+
|
616
|
+
algolia_ensure_init(options, settings)
|
617
|
+
index_name = algolia_index_name(options)
|
618
|
+
res = AlgoliaSearch.client.clear_objects(index_name)
|
619
|
+
|
620
|
+
if synchronous || options[:synchronous]
|
621
|
+
AlgoliaSearch.client.wait_for_task(index_name, res.task_id)
|
622
|
+
end
|
665
623
|
end
|
666
624
|
nil
|
667
625
|
end
|
668
626
|
|
627
|
+
|
669
628
|
def algolia_raw_search(q, params = {})
|
670
|
-
|
629
|
+
index_name_base = params.delete(:index) ||
|
671
630
|
params.delete('index') ||
|
672
|
-
params.delete(:slave) ||
|
673
|
-
params.delete('slave') ||
|
674
631
|
params.delete(:replica) ||
|
675
632
|
params.delete('replica')
|
676
|
-
|
677
|
-
|
633
|
+
|
634
|
+
opts = algoliasearch_options
|
635
|
+
unless index_name_base.nil?
|
636
|
+
algolia_configurations.each do |o, s|
|
637
|
+
if o[:index_name].to_s == index_name_base.to_s
|
638
|
+
opts = o
|
639
|
+
ensure_algolia_index(index_name_base)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
index_name = algolia_index_name(opts, index_name_base)
|
645
|
+
AlgoliaSearch.client.search_single_index(index_name,Hash[params.to_h.map { |k,v| [k.to_s, v.to_s] }].merge({query: q})).to_hash
|
678
646
|
end
|
679
647
|
|
680
648
|
module AdditionalMethods
|
@@ -690,7 +658,7 @@ module AlgoliaSearch
|
|
690
658
|
end
|
691
659
|
|
692
660
|
def algolia_facets
|
693
|
-
@algolia_json[
|
661
|
+
@algolia_json[:facets]
|
694
662
|
end
|
695
663
|
|
696
664
|
private
|
@@ -701,12 +669,12 @@ module AlgoliaSearch
|
|
701
669
|
|
702
670
|
def algolia_search(q, params = {})
|
703
671
|
if AlgoliaSearch.configuration[:pagination_backend]
|
704
|
-
# kaminari and
|
672
|
+
# kaminari, will_paginate, and pagy start pagination at 1, Algolia starts at 0
|
705
673
|
params[:page] = (params.delete('page') || params.delete(:page)).to_i
|
706
674
|
params[:page] -= 1 if params[:page].to_i > 0
|
707
675
|
end
|
708
676
|
json = algolia_raw_search(q, params)
|
709
|
-
hit_ids = json[
|
677
|
+
hit_ids = json[:hits].map { |hit| hit[:objectID] }
|
710
678
|
if defined?(::Mongoid::Document) && self.include?(::Mongoid::Document)
|
711
679
|
condition_key = algolia_object_id_method.in
|
712
680
|
else
|
@@ -715,18 +683,18 @@ module AlgoliaSearch
|
|
715
683
|
results_by_id = algoliasearch_options[:type].where(condition_key => hit_ids).index_by do |hit|
|
716
684
|
algolia_object_id_of(hit)
|
717
685
|
end
|
718
|
-
results = json[
|
719
|
-
o = results_by_id[hit[
|
686
|
+
results = json[:hits].map do |hit|
|
687
|
+
o = results_by_id[hit[:objectID].to_s]
|
720
688
|
if o
|
721
|
-
o.highlight_result = hit[
|
722
|
-
o.snippet_result = hit[
|
689
|
+
o.highlight_result = hit[:_highlightResult]
|
690
|
+
o.snippet_result = hit[:_snippetResult]
|
723
691
|
o
|
724
692
|
end
|
725
693
|
end.compact
|
726
694
|
# Algolia has a default limit of 1000 retrievable hits
|
727
|
-
total_hits = json[
|
728
|
-
json[
|
729
|
-
res = AlgoliaSearch::Pagination.create(results, total_hits, algoliasearch_options.merge({ :page => json[
|
695
|
+
total_hits = json[:nbHits].to_i < json[:nbPages].to_i * json[:hitsPerPage].to_i ?
|
696
|
+
json[:nbHits].to_i: json[:nbPages].to_i * json[:hitsPerPage].to_i
|
697
|
+
res = AlgoliaSearch::Pagination.create(results, total_hits, algoliasearch_options.merge({ :page => json[:page].to_i + 1, :per_page => json[:hitsPerPage] }))
|
730
698
|
res.extend(AdditionalMethods)
|
731
699
|
res.send(:algolia_init_raw_answer, json)
|
732
700
|
res
|
@@ -735,19 +703,18 @@ module AlgoliaSearch
|
|
735
703
|
def algolia_search_for_facet_values(facet, text, params = {})
|
736
704
|
index_name = params.delete(:index) ||
|
737
705
|
params.delete('index') ||
|
738
|
-
params.delete(:slave) ||
|
739
|
-
params.delete('slave') ||
|
740
706
|
params.delete(:replica) ||
|
741
707
|
params.delete('replicas')
|
742
|
-
|
743
|
-
|
744
|
-
|
708
|
+
index_name ||= algolia_index_name(algoliasearch_options)
|
709
|
+
req = Algolia::Search::SearchForFacetValuesRequest.new({facet_query: text, params: params.to_query})
|
710
|
+
|
711
|
+
AlgoliaSearch.client.search_for_facet_values(index_name, facet, req).facet_hits
|
745
712
|
end
|
746
713
|
|
747
714
|
# deprecated (renaming)
|
748
715
|
alias :algolia_search_facet :algolia_search_for_facet_values
|
749
716
|
|
750
|
-
def
|
717
|
+
def ensure_algolia_index(name = nil)
|
751
718
|
if name
|
752
719
|
algolia_configurations.each do |o, s|
|
753
720
|
return algolia_ensure_init(o, s) if o[:index_name].to_s == name.to_s
|
@@ -757,9 +724,9 @@ module AlgoliaSearch
|
|
757
724
|
algolia_ensure_init
|
758
725
|
end
|
759
726
|
|
760
|
-
def algolia_index_name(options = nil)
|
727
|
+
def algolia_index_name(options = nil, index_name = nil)
|
761
728
|
options ||= algoliasearch_options
|
762
|
-
name = options[:index_name] || model_name.to_s.gsub('::', '_')
|
729
|
+
name = index_name || options[:index_name] || model_name.to_s.gsub('::', '_')
|
763
730
|
name = "#{name}_#{Rails.env.to_s}" if options[:per_environment]
|
764
731
|
name
|
765
732
|
end
|
@@ -770,17 +737,16 @@ module AlgoliaSearch
|
|
770
737
|
# Loop over each index to see if a attribute used in records has changed
|
771
738
|
algolia_configurations.each do |options, settings|
|
772
739
|
next if algolia_indexing_disabled?(options)
|
773
|
-
next if options[:
|
740
|
+
next if options[:replica]
|
774
741
|
return true if algolia_object_id_changed?(object, options)
|
775
742
|
settings.get_attribute_names(object).each do |k|
|
776
|
-
return true if algolia_attribute_changed?(object, k)
|
777
|
-
# return true if !object.respond_to?(changed_method) || object.send(changed_method)
|
743
|
+
return true if algolia_attribute_changed?(object, k, true)
|
778
744
|
end
|
779
745
|
[options[:if], options[:unless]].each do |condition|
|
780
746
|
case condition
|
781
747
|
when nil
|
782
748
|
when String, Symbol
|
783
|
-
return true if algolia_attribute_changed?(object, condition)
|
749
|
+
return true if algolia_attribute_changed?(object, condition, true)
|
784
750
|
else
|
785
751
|
# if the :if, :unless condition is a anything else,
|
786
752
|
# we have no idea whether we should reindex or not
|
@@ -795,36 +761,46 @@ module AlgoliaSearch
|
|
795
761
|
|
796
762
|
protected
|
797
763
|
|
798
|
-
def algolia_ensure_init(options = nil, settings = nil,
|
764
|
+
def algolia_ensure_init(options = nil, settings = nil, index_settings_hash = nil)
|
799
765
|
raise ArgumentError.new('No `algoliasearch` block found in your model.') if algoliasearch_settings.nil?
|
800
766
|
|
801
|
-
@
|
767
|
+
@algolia_indexes_init ||= {}
|
802
768
|
|
803
769
|
options ||= algoliasearch_options
|
804
770
|
settings ||= algoliasearch_settings
|
805
771
|
|
806
|
-
return
|
772
|
+
return if @algolia_indexes_init[settings]
|
807
773
|
|
808
|
-
|
774
|
+
index_name = algolia_index_name(options)
|
809
775
|
|
810
|
-
current_settings = @algolia_indexes[settings].get_settings(:getVersion => 1) rescue nil # if the index doesn't exist
|
811
776
|
|
812
|
-
|
813
|
-
|
777
|
+
index_settings_hash ||= settings.to_settings.to_hash
|
778
|
+
index_settings_hash = options[:primary_settings].to_settings.to_hash.merge(index_settings_hash) if options[:inherit]
|
779
|
+
replicas = index_settings_hash.delete(:replicas) || index_settings_hash.delete('replicas')
|
780
|
+
index_settings_hash[:replicas] = replicas unless replicas.nil? || options[:inherit]
|
814
781
|
|
815
782
|
options[:check_settings] = true if options[:check_settings].nil?
|
816
783
|
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
784
|
+
current_settings = if options[:check_settings] && !algolia_indexing_disabled?(options)
|
785
|
+
AlgoliaSearch.client.get_settings(index_name, {:getVersion => 1}).to_hash rescue nil # if the index doesn't exist
|
786
|
+
end
|
787
|
+
|
788
|
+
if !algolia_indexing_disabled?(options) && options[:check_settings] && algoliasearch_settings_changed?(current_settings, index_settings_hash)
|
789
|
+
s = index_settings_hash.map do |k, v|
|
790
|
+
[settings.setting_name(k), v]
|
791
|
+
end.to_h
|
792
|
+
|
793
|
+
synonyms = s.delete("synonyms") || s.delete(:synonyms)
|
794
|
+
unless synonyms.nil? || synonyms.empty?
|
795
|
+
resp = AlgoliaSearch.client.save_synonyms(index_name,synonyms.map {|s| Algolia::Search::SynonymHit.new({object_id: s.join("-"), synonyms: s, type: "synonym"}) } )
|
796
|
+
AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if options[:synchronous]
|
797
|
+
end
|
798
|
+
|
799
|
+
resp = AlgoliaSearch.client.set_settings(index_name, Algolia::Search::IndexSettings.new(s))
|
800
|
+
AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if options[:synchronous]
|
825
801
|
end
|
826
802
|
|
827
|
-
|
803
|
+
return
|
828
804
|
end
|
829
805
|
|
830
806
|
private
|
@@ -857,17 +833,19 @@ module AlgoliaSearch
|
|
857
833
|
end
|
858
834
|
|
859
835
|
def algolia_object_id_changed?(o, options = nil)
|
860
|
-
changed = algolia_attribute_changed?(o, algolia_object_id_method(options))
|
836
|
+
changed = algolia_attribute_changed?(o, algolia_object_id_method(options), false)
|
861
837
|
changed.nil? ? false : changed
|
862
838
|
end
|
863
839
|
|
864
840
|
def algoliasearch_settings_changed?(prev, current)
|
865
841
|
return true if prev.nil?
|
866
842
|
current.each do |k, v|
|
867
|
-
prev_v = prev[k.to_s]
|
843
|
+
prev_v = prev[k.to_sym] || prev[k.to_s]
|
868
844
|
if v.is_a?(Array) and prev_v.is_a?(Array)
|
869
845
|
# compare array of strings, avoiding symbols VS strings comparison
|
870
846
|
return true if v.map { |x| x.to_s } != prev_v.map { |x| x.to_s }
|
847
|
+
elsif v.blank? # blank? check is needed to compare [] and null
|
848
|
+
return true unless prev_v.blank?
|
871
849
|
else
|
872
850
|
return true if prev_v != v
|
873
851
|
end
|
@@ -936,7 +914,7 @@ module AlgoliaSearch
|
|
936
914
|
def algolia_find_in_batches(batch_size, &block)
|
937
915
|
if (defined?(::ActiveRecord) && ancestors.include?(::ActiveRecord::Base)) || respond_to?(:find_in_batches)
|
938
916
|
find_in_batches(:batch_size => batch_size, &block)
|
939
|
-
elsif defined?(::Sequel) && self < Sequel::Model
|
917
|
+
elsif defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
940
918
|
dataset.extension(:pagination).each_page(batch_size, &block)
|
941
919
|
else
|
942
920
|
# don't worry, mongoid has its own underlying cursor/streaming mechanism
|
@@ -952,7 +930,7 @@ module AlgoliaSearch
|
|
952
930
|
end
|
953
931
|
end
|
954
932
|
|
955
|
-
def algolia_attribute_changed?(object, attr_name)
|
933
|
+
def algolia_attribute_changed?(object, attr_name, default)
|
956
934
|
# if one of two method is implemented, we return its result
|
957
935
|
# true/false means whether it has changed or not
|
958
936
|
# +#{attr_name}_changed?+ always defined for automatic attributes but deprecated after Rails 5.2
|
@@ -981,8 +959,8 @@ module AlgoliaSearch
|
|
981
959
|
return object.send("will_save_change_to_#{attr_name}?")
|
982
960
|
end
|
983
961
|
|
984
|
-
# We don't know if the attribute has changed, so
|
985
|
-
|
962
|
+
# We don't know if the attribute has changed, so return the default passed
|
963
|
+
default
|
986
964
|
end
|
987
965
|
|
988
966
|
def automatic_changed_method?(object, method_name)
|
@@ -1049,7 +1027,7 @@ module AlgoliaSearch
|
|
1049
1027
|
# algolia_must_reindex flag is reset after every commit as part. If we must reindex at any point in
|
1050
1028
|
# a stransaction, keep flag set until it is explicitly unset
|
1051
1029
|
@algolia_must_reindex ||=
|
1052
|
-
if defined?(::Sequel) && is_a?(Sequel::Model)
|
1030
|
+
if defined?(::Sequel) && defined?(::Sequel::Model) && is_a?(Sequel::Model)
|
1053
1031
|
new? || self.class.algolia_must_reindex?(self)
|
1054
1032
|
else
|
1055
1033
|
new_record? || self.class.algolia_must_reindex?(self)
|