algoliasearch-rails 1.20.4 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- require 'algoliasearch'
1
+ require 'algolia'
2
2
 
3
3
  require 'algoliasearch/version'
4
4
  require 'algoliasearch/utilities'
@@ -47,34 +47,60 @@ module AlgoliaSearch
47
47
  end
48
48
 
49
49
  class IndexSettings
50
+ DEFAULT_BATCH_SIZE = 1000
50
51
 
51
52
  # AlgoliaSearch settings
52
- OPTIONS = [:minWordSizefor1Typo, :minWordSizefor2Typos, :typoTolerance,
53
- :hitsPerPage, :attributesToRetrieve,
54
- :attributesToHighlight, :attributesToSnippet, :attributesToIndex, :searchableAttributes,
55
- :highlightPreTag, :highlightPostTag,
56
- :ranking, :customRanking, :queryType, :attributesForFaceting,
57
- :separatorsToIndex, :optionalWords, :attributeForDistinct,
58
- :synonyms, :placeholders, :removeWordsIfNoResults, :replaceSynonymsInHighlight,
59
- :unretrievableAttributes, :disableTypoToleranceOnWords, :disableTypoToleranceOnAttributes, :altCorrections,
60
- :ignorePlurals, :maxValuesPerFacet, :distinct, :numericAttributesToIndex, :numericAttributesForFiltering,
61
- :allowTyposOnNumericTokens, :allowCompressionOfIntegerArray,
62
- :advancedSyntax, :disablePrefixOnAttributes, :disableTypoToleranceOnAttributes,
63
- :paginationLimitedTo]
53
+ OPTIONS = [
54
+ # Attributes
55
+ :searchableAttributes, :attributesForFaceting, :unretrievableAttributes, :attributesToRetrieve,
56
+ # Ranking
57
+ :ranking, :customRanking, :relevancyStrictness, # Replicas are handled via `add_replica`
58
+ # Faceting
59
+ :maxValuesPerFacet, :sortFacetValuesBy,
60
+ # Highlighting / Snippeting
61
+ :attributesToHighlight, :attributesToSnippet, :highlightPreTag, :highlightPostTag,
62
+ :snippetEllipsisText, :restrictHighlightAndSnippetArrays,
63
+ # Pagination
64
+ :hitsPerPage, :paginationLimitedTo,
65
+ # Typo
66
+ :minWordSizefor1Typo, :minWordSizefor2Typos, :typoTolerance, :allowTyposOnNumericTokens,
67
+ :disableTypoToleranceOnAttributes, :disableTypoToleranceOnWords, :separatorsToIndex,
68
+ # Language
69
+ :ignorePlurals, :removeStopWords, :camelCaseAttributes, :decompoundedAttributes,
70
+ :keepDiacriticsOnCharacters, :queryLanguages, :indexLanguages,
71
+ # Query Rules
72
+ :enableRules,
73
+ # Query Strategy
74
+ :queryType, :removeWordsIfNoResults, :advancedSyntax, :optionalWords,
75
+ :disablePrefixOnAttributes, :disableExactOnAttributes, :exactOnSingleWordQuery, :alternativesAsExact,
76
+ # Performance
77
+ :numericAttributesForFiltering, :allowCompressionOfIntegerArray,
78
+ # Advanced
79
+ :attributeForDistinct, :distinct, :replaceSynonymsInHighlight, :minProximity, :responseFields,
80
+ :maxFacetHits,
81
+
82
+ # Rails-specific
83
+ :synonyms, :placeholders, :altCorrections,
84
+ ]
64
85
  OPTIONS.each do |k|
65
86
  define_method k do |v|
66
87
  instance_variable_set("@#{k}", v)
67
88
  end
68
89
  end
69
90
 
70
- def initialize(options, block)
91
+ def initialize(options, &block)
71
92
  @options = options
72
- instance_exec(&block) if block
93
+ instance_exec(&block) if block_given?
94
+ end
95
+
96
+ def use_serializer(serializer)
97
+ @serializer = serializer
98
+ # instance_variable_set("@serializer", serializer)
73
99
  end
74
100
 
75
101
  def attribute(*names, &block)
76
102
  raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
77
- raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
103
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
78
104
  @attributes ||= {}
79
105
  names.flatten.each do |name|
80
106
  @attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
@@ -84,7 +110,7 @@ module AlgoliaSearch
84
110
 
85
111
  def add_attribute(*names, &block)
86
112
  raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
87
- raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
113
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
88
114
  @additional_attributes ||= {}
89
115
  names.each do |name|
90
116
  @additional_attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
@@ -97,7 +123,7 @@ module AlgoliaSearch
97
123
  end
98
124
 
99
125
  def is_sequel?(object)
100
- defined?(::Sequel) && object.class < ::Sequel::Model
126
+ defined?(::Sequel) && defined?(::Sequel::Model) && object.class < ::Sequel::Model
101
127
  end
102
128
 
103
129
  def is_active_record?(object)
@@ -118,15 +144,7 @@ module AlgoliaSearch
118
144
  end
119
145
 
120
146
  def get_attribute_names(object)
121
- res = if @attributes.nil? || @attributes.length == 0
122
- get_default_attributes(object).keys
123
- else
124
- @attributes.keys
125
- end
126
-
127
- res += @additional_attributes.keys if @additional_attributes
128
-
129
- res
147
+ get_attributes(object).keys
130
148
  end
131
149
 
132
150
  def attributes_to_hash(attributes, object)
@@ -138,19 +156,27 @@ module AlgoliaSearch
138
156
  end
139
157
 
140
158
  def get_attributes(object)
141
- attributes = if @attributes.nil? || @attributes.length == 0
142
- get_default_attributes(object)
159
+ # If a serializer is set, we ignore attributes
160
+ # everything should be done via the serializer
161
+ if not @serializer.nil?
162
+ attributes = @serializer.new(object).attributes
143
163
  else
144
- if is_active_record?(object)
145
- object.class.unscoped do
146
- attributes_to_hash(@attributes, object)
147
- end
164
+ if @attributes.nil? || @attributes.length == 0
165
+ # no `attribute ...` have been configured, use the default attributes of the model
166
+ attributes = get_default_attributes(object)
148
167
  else
149
- attributes_to_hash(@attributes, object)
168
+ # at least 1 `attribute ...` has been configured, therefore use ONLY the one configured
169
+ if is_active_record?(object)
170
+ object.class.unscoped do
171
+ attributes = attributes_to_hash(@attributes, object)
172
+ end
173
+ else
174
+ attributes = attributes_to_hash(@attributes, object)
175
+ end
150
176
  end
151
177
  end
152
178
 
153
- attributes.merge!(attributes_to_hash(@additional_attributes, object))
179
+ attributes.merge!(attributes_to_hash(@additional_attributes, object)) if @additional_attributes
154
180
 
155
181
  if @options[:sanitize]
156
182
  sanitizer = begin
@@ -185,7 +211,7 @@ module AlgoliaSearch
185
211
  def encode_attributes(v)
186
212
  case v
187
213
  when String
188
- v.force_encoding('utf-8')
214
+ v.dup.force_encoding('utf-8')
189
215
  when Hash
190
216
  v.each { |key, value| v[key] = encode_attributes(value) }
191
217
  when Array
@@ -195,15 +221,15 @@ module AlgoliaSearch
195
221
  end
196
222
  end
197
223
 
198
- def geoloc(lat_attr, lng_attr)
199
- raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
224
+ def geoloc(lat_attr = nil, lng_attr = nil, &block)
225
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
200
226
  add_attribute :_geoloc do |o|
201
- { :lat => o.send(lat_attr).to_f, :lng => o.send(lng_attr).to_f }
227
+ block_given? ? o.instance_eval(&block) : { :lat => o.send(lat_attr).to_f, :lng => o.send(lng_attr).to_f }
202
228
  end
203
229
  end
204
230
 
205
231
  def tags(*args, &block)
206
- raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
232
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
207
233
  add_attribute :_tags do |o|
208
234
  v = block_given? ? o.instance_eval(&block) : args
209
235
  v.is_a?(Array) ? v : [v]
@@ -220,16 +246,12 @@ module AlgoliaSearch
220
246
  v = get_setting(k)
221
247
  settings[k] = v if !v.nil?
222
248
  end
223
- if !@options[:slave] && !@options[:replica]
224
- settings[:slaves] = additional_indexes.select { |opts, s| opts[:slave] }.map do |opts, s|
225
- name = opts[:index_name]
226
- name = "#{name}_#{Rails.env.to_s}" if opts[:per_environment]
227
- name
228
- end
229
- settings.delete(:slaves) if settings[:slaves].empty?
249
+
250
+ if !@options[:replica]
230
251
  settings[:replicas] = additional_indexes.select { |opts, s| opts[:replica] }.map do |opts, s|
231
252
  name = opts[:index_name]
232
253
  name = "#{name}_#{Rails.env.to_s}" if opts[:per_environment]
254
+ name = "virtual(#{name})" if opts[:virtual]
233
255
  name
234
256
  end
235
257
  settings.delete(:replicas) if settings[:replicas].empty?
@@ -238,27 +260,20 @@ module AlgoliaSearch
238
260
  end
239
261
 
240
262
  def add_index(index_name, options = {}, &block)
241
- raise ArgumentError.new('Cannot specify additional index on a replica index') if @options[:slave] || @options[:replica]
263
+ raise ArgumentError.new('Cannot specify additional index on a replica index') if @options[:replica]
242
264
  raise ArgumentError.new('No block given') if !block_given?
243
265
  raise ArgumentError.new('Options auto_index and auto_remove cannot be set on nested indexes') if options[:auto_index] || options[:auto_remove]
244
266
  @additional_indexes ||= {}
245
- 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] })
246
267
  options[:index_name] = index_name
247
- @additional_indexes[options] = IndexSettings.new(options, Proc.new)
268
+ @additional_indexes[options] = IndexSettings.new(options, &block)
248
269
  end
249
270
 
250
271
  def add_replica(index_name, options = {}, &block)
251
- raise ArgumentError.new('Cannot specify additional replicas on a replica index') if @options[:slave] || @options[:replica]
272
+ raise ArgumentError.new('Cannot specify additional replicas on a replica index') if @options[:replica]
252
273
  raise ArgumentError.new('No block given') if !block_given?
253
274
  add_index(index_name, options.merge({ :replica => true, :primary_settings => self }), &block)
254
275
  end
255
276
 
256
- def add_slave(index_name, options = {}, &block)
257
- raise ArgumentError.new('Cannot specify additional slaves on a slave index') if @options[:slave] || @options[:replica]
258
- raise ArgumentError.new('No block given') if !block_given?
259
- add_index(index_name, options.merge({ :slave => true, :primary_settings => self }), &block)
260
- end
261
-
262
277
  def additional_indexes
263
278
  @additional_indexes || {}
264
279
  end
@@ -276,11 +291,11 @@ module AlgoliaSearch
276
291
  # are correctly logged or thrown depending on the `raise_on_failure` option
277
292
  class SafeIndex
278
293
  def initialize(name, raise_on_failure)
279
- @index = ::Algolia::Index.new(name)
294
+ @index = AlgoliaSearch.client.init_index(name)
280
295
  @raise_on_failure = raise_on_failure.nil? || raise_on_failure
281
296
  end
282
297
 
283
- ::Algolia::Index.instance_methods(false).each do |m|
298
+ ::Algolia::Search::Index.instance_methods(false).each do |m|
284
299
  define_method(m) do |*args, &block|
285
300
  SafeIndex.log_or_throw(m, @raise_on_failure) do
286
301
  @index.send(m, *args, &block)
@@ -301,7 +316,7 @@ module AlgoliaSearch
301
316
  SafeIndex.log_or_throw(:get_settings, @raise_on_failure) do
302
317
  begin
303
318
  @index.get_settings(*args)
304
- rescue Algolia::AlgoliaError => e
319
+ rescue Algolia::AlgoliaHttpError => e
305
320
  return {} if e.code == 404 # not fatal
306
321
  raise e
307
322
  end
@@ -311,7 +326,7 @@ module AlgoliaSearch
311
326
  # expose move as well
312
327
  def self.move_index(old_name, new_name)
313
328
  SafeIndex.log_or_throw(:move_index, true) do
314
- ::Algolia.move_index(old_name, new_name)
329
+ AlgoliaSearch.client.move_index(old_name, new_name)
315
330
  end
316
331
  end
317
332
 
@@ -361,13 +376,13 @@ module AlgoliaSearch
361
376
  end
362
377
 
363
378
  def algoliasearch(options = {}, &block)
364
- self.algoliasearch_settings = IndexSettings.new(options, block_given? ? Proc.new : nil)
379
+ self.algoliasearch_settings = IndexSettings.new(options, &block)
365
380
  self.algoliasearch_options = { :type => algolia_full_const_get(model_name.to_s), :per_page => algoliasearch_settings.get_setting(:hitsPerPage) || 10, :page => 1 }.merge(options)
366
381
 
367
382
  attr_accessor :highlight_result, :snippet_result
368
383
 
369
384
  if options[:synchronous] == true
370
- if defined?(::Sequel) && self < Sequel::Model
385
+ if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
371
386
  class_eval do
372
387
  copy_after_validation = instance_method(:after_validation)
373
388
  define_method(:after_validation) do |*args|
@@ -398,11 +413,10 @@ module AlgoliaSearch
398
413
  end
399
414
  end
400
415
  unless options[:auto_index] == false
401
- if defined?(::Sequel) && self < Sequel::Model
416
+ if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
402
417
  class_eval do
403
418
  copy_after_validation = instance_method(:after_validation)
404
419
  copy_before_save = instance_method(:before_save)
405
- copy_after_commit = instance_method(:after_commit)
406
420
 
407
421
  define_method(:after_validation) do |*args|
408
422
  super(*args)
@@ -416,10 +430,23 @@ module AlgoliaSearch
416
430
  super(*args)
417
431
  end
418
432
 
419
- define_method(:after_commit) do |*args|
420
- super(*args)
421
- copy_after_commit.bind(self).call
422
- algolia_perform_index_tasks
433
+ sequel_version = Gem::Version.new(Sequel.version)
434
+ if sequel_version >= Gem::Version.new('4.0.0') && sequel_version < Gem::Version.new('5.0.0')
435
+ copy_after_commit = instance_method(:after_commit)
436
+ define_method(:after_commit) do |*args|
437
+ super(*args)
438
+ copy_after_commit.bind(self).call
439
+ algolia_perform_index_tasks
440
+ end
441
+ else
442
+ copy_after_save = instance_method(:after_save)
443
+ define_method(:after_save) do |*args|
444
+ super(*args)
445
+ copy_after_save.bind(self).call
446
+ self.db.after_commit do
447
+ algolia_perform_index_tasks
448
+ end
449
+ end
423
450
  end
424
451
  end
425
452
  else
@@ -433,7 +460,7 @@ module AlgoliaSearch
433
460
  end
434
461
  end
435
462
  unless options[:auto_remove] == false
436
- if defined?(::Sequel) && self < Sequel::Model
463
+ if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
437
464
  class_eval do
438
465
  copy_after_destroy = instance_method(:after_destroy)
439
466
 
@@ -466,12 +493,12 @@ module AlgoliaSearch
466
493
  Thread.current["algolia_without_auto_index_scope_for_#{self.model_name}"]
467
494
  end
468
495
 
469
- def algolia_reindex!(batch_size = 1000, synchronous = false)
496
+ def algolia_reindex!(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
470
497
  return if algolia_without_auto_index_scope
471
498
  algolia_configurations.each do |options, settings|
472
499
  next if algolia_indexing_disabled?(options)
473
500
  index = algolia_ensure_init(options, settings)
474
- next if options[:slave] || options[:replica]
501
+ next if options[:replica]
475
502
  last_task = nil
476
503
 
477
504
  algolia_find_in_batches(batch_size) do |group|
@@ -491,38 +518,44 @@ module AlgoliaSearch
491
518
  end
492
519
  last_task = index.save_objects(objects)
493
520
  end
494
- index.wait_task(last_task["taskID"]) if last_task and (synchronous || options[:synchronous])
521
+ index.wait_task(last_task.raw_response["taskID"]) if last_task and (synchronous || options[:synchronous])
495
522
  end
496
523
  nil
497
524
  end
498
525
 
499
526
  # reindex whole database using a extra temporary index + move operation
500
- def algolia_reindex(batch_size = 1000, synchronous = false)
527
+ def algolia_reindex(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
501
528
  return if algolia_without_auto_index_scope
502
529
  algolia_configurations.each do |options, settings|
503
530
  next if algolia_indexing_disabled?(options)
504
- next if options[:slave] || options[:replica]
531
+ next if options[:replica]
505
532
 
506
533
  # fetch the master settings
507
534
  master_index = algolia_ensure_init(options, settings)
508
535
  master_settings = master_index.get_settings rescue {} # if master doesn't exist yet
536
+ master_exists = master_settings != {}
509
537
  master_settings.merge!(JSON.parse(settings.to_settings.to_json)) # convert symbols to strings
510
538
 
511
539
  # remove the replicas of the temporary index
512
- master_settings.delete :slaves
513
- master_settings.delete 'slaves'
514
540
  master_settings.delete :replicas
515
541
  master_settings.delete 'replicas'
516
542
 
517
543
  # init temporary index
518
- index_name = algolia_index_name(options)
519
- tmp_options = options.merge({ :index_name => "#{index_name}.tmp" })
544
+ src_index_name = algolia_index_name(options)
545
+ tmp_index_name = "#{src_index_name}.tmp"
546
+ tmp_options = options.merge({ :index_name => tmp_index_name })
520
547
  tmp_options.delete(:per_environment) # already included in the temporary index_name
521
548
  tmp_settings = settings.dup
522
- tmp_index = algolia_ensure_init(tmp_options, tmp_settings, master_settings)
549
+
550
+ if options[:check_settings] == false && master_exists
551
+ AlgoliaSearch.client.copy_index!(src_index_name, tmp_index_name, { scope: %w[settings synonyms rules] })
552
+ tmp_index = SafeIndex.new(tmp_index_name, !!options[:raise_on_failure])
553
+ else
554
+ tmp_index = algolia_ensure_init(tmp_options, tmp_settings, master_settings)
555
+ end
523
556
 
524
557
  algolia_find_in_batches(batch_size) do |group|
525
- if algolia_conditional_index?(tmp_options)
558
+ if algolia_conditional_index?(options)
526
559
  # select only indexable objects
527
560
  group = group.select { |o| algolia_indexable?(o, tmp_options) }
528
561
  end
@@ -530,19 +563,36 @@ module AlgoliaSearch
530
563
  tmp_index.save_objects(objects)
531
564
  end
532
565
 
533
- move_task = SafeIndex.move_index(tmp_index.name, index_name)
534
- master_index.wait_task(move_task["taskID"]) if synchronous || options[:synchronous]
566
+ move_task = SafeIndex.move_index(tmp_index.name, src_index_name)
567
+ master_index.wait_task(move_task.raw_response["taskID"]) if synchronous || options[:synchronous]
535
568
  end
536
569
  nil
537
570
  end
538
571
 
572
+ def algolia_set_settings(synchronous = false)
573
+ algolia_configurations.each do |options, settings|
574
+ if options[:primary_settings] && options[:inherit]
575
+ primary = options[:primary_settings].to_settings
576
+ primary.delete :replicas
577
+ primary.delete 'replicas'
578
+ final_settings = primary.merge(settings.to_settings)
579
+ else
580
+ final_settings = settings.to_settings
581
+ end
582
+
583
+ index = SafeIndex.new(algolia_index_name(options), true)
584
+ task = index.set_settings(final_settings)
585
+ index.wait_task(task.raw_response["taskID"]) if synchronous
586
+ end
587
+ end
588
+
539
589
  def algolia_index_objects(objects, synchronous = false)
540
590
  algolia_configurations.each do |options, settings|
541
591
  next if algolia_indexing_disabled?(options)
542
592
  index = algolia_ensure_init(options, settings)
543
- next if options[:slave] || options[:replica]
593
+ next if options[:replica]
544
594
  task = index.save_objects(objects.map { |o| settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o, options) })
545
- index.wait_task(task["taskID"]) if synchronous || options[:synchronous]
595
+ index.wait_task(task.raw_response["taskID"]) if synchronous || options[:synchronous]
546
596
  end
547
597
  end
548
598
 
@@ -552,13 +602,13 @@ module AlgoliaSearch
552
602
  next if algolia_indexing_disabled?(options)
553
603
  object_id = algolia_object_id_of(object, options)
554
604
  index = algolia_ensure_init(options, settings)
555
- next if options[:slave] || options[:replica]
605
+ next if options[:replica]
556
606
  if algolia_indexable?(object, options)
557
607
  raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
558
608
  if synchronous || options[:synchronous]
559
- index.add_object!(settings.get_attributes(object), object_id)
609
+ index.save_object!(settings.get_attributes(object).merge 'objectID' => algolia_object_id_of(object, options))
560
610
  else
561
- index.add_object(settings.get_attributes(object), object_id)
611
+ index.save_object(settings.get_attributes(object).merge 'objectID' => algolia_object_id_of(object, options))
562
612
  end
563
613
  elsif algolia_conditional_index?(options) && !object_id.blank?
564
614
  # remove non-indexable objects
@@ -579,7 +629,7 @@ module AlgoliaSearch
579
629
  algolia_configurations.each do |options, settings|
580
630
  next if algolia_indexing_disabled?(options)
581
631
  index = algolia_ensure_init(options, settings)
582
- next if options[:slave] || options[:replica]
632
+ next if options[:replica]
583
633
  if synchronous || options[:synchronous]
584
634
  index.delete_object!(object_id)
585
635
  else
@@ -593,8 +643,8 @@ module AlgoliaSearch
593
643
  algolia_configurations.each do |options, settings|
594
644
  next if algolia_indexing_disabled?(options)
595
645
  index = algolia_ensure_init(options, settings)
596
- next if options[:slave] || options[:replica]
597
- synchronous || options[:synchronous] ? index.clear! : index.clear
646
+ next if options[:replica]
647
+ synchronous || options[:synchronous] ? index.clear_objects! : index.clear_objects
598
648
  @algolia_indexes[settings] = nil
599
649
  end
600
650
  nil
@@ -603,8 +653,6 @@ module AlgoliaSearch
603
653
  def algolia_raw_search(q, params = {})
604
654
  index_name = params.delete(:index) ||
605
655
  params.delete('index') ||
606
- params.delete(:slave) ||
607
- params.delete('slave') ||
608
656
  params.delete(:replica) ||
609
657
  params.delete('replica')
610
658
  index = algolia_index(index_name)
@@ -669,13 +717,11 @@ module AlgoliaSearch
669
717
  def algolia_search_for_facet_values(facet, text, params = {})
670
718
  index_name = params.delete(:index) ||
671
719
  params.delete('index') ||
672
- params.delete(:slave) ||
673
- params.delete('slave') ||
674
720
  params.delete(:replica) ||
675
721
  params.delete('replicas')
676
722
  index = algolia_index(index_name)
677
723
  query = Hash[params.map { |k, v| [k.to_s, v.to_s] }]
678
- index.search_facet(facet, text, query)['facetHits']
724
+ index.search_for_facet_values(facet, text, query)['facetHits']
679
725
  end
680
726
 
681
727
  # deprecated (renaming)
@@ -699,19 +745,22 @@ module AlgoliaSearch
699
745
  end
700
746
 
701
747
  def algolia_must_reindex?(object)
748
+ # use +algolia_dirty?+ method if implemented
749
+ return object.send(:algolia_dirty?) if (object.respond_to?(:algolia_dirty?))
750
+ # Loop over each index to see if a attribute used in records has changed
702
751
  algolia_configurations.each do |options, settings|
703
- next if options[:slave] || options[:replica]
752
+ next if algolia_indexing_disabled?(options)
753
+ next if options[:replica]
704
754
  return true if algolia_object_id_changed?(object, options)
705
755
  settings.get_attribute_names(object).each do |k|
706
- changed_method = "#{k}_changed?"
707
- return true if !object.respond_to?(changed_method) || object.send(changed_method)
756
+ return true if algolia_attribute_changed?(object, k)
757
+ # return true if !object.respond_to?(changed_method) || object.send(changed_method)
708
758
  end
709
759
  [options[:if], options[:unless]].each do |condition|
710
760
  case condition
711
761
  when nil
712
762
  when String, Symbol
713
- changed_method = "#{condition}_changed?"
714
- return true if !object.respond_to?(changed_method) || object.send(changed_method)
763
+ return true if algolia_attribute_changed?(object, condition)
715
764
  else
716
765
  # if the :if, :unless condition is a anything else,
717
766
  # we have no idea whether we should reindex or not
@@ -720,6 +769,7 @@ module AlgoliaSearch
720
769
  end
721
770
  end
722
771
  end
772
+ # By default, we don't reindex
723
773
  return false
724
774
  end
725
775
 
@@ -737,19 +787,21 @@ module AlgoliaSearch
737
787
 
738
788
  @algolia_indexes[settings] = SafeIndex.new(algolia_index_name(options), algoliasearch_options[:raise_on_failure])
739
789
 
740
- current_settings = @algolia_indexes[settings].get_settings(:getVersion => 1) rescue nil # if the index doesn't exist
741
-
742
790
  index_settings ||= settings.to_settings
743
791
  index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit]
792
+ replicas = index_settings.delete(:replicas) ||
793
+ index_settings.delete('replicas')
794
+ index_settings[:replicas] = replicas unless replicas.nil? || options[:inherit]
744
795
 
745
- if !algolia_indexing_disabled?(options) && algoliasearch_settings_changed?(current_settings, index_settings)
746
- used_slaves = !current_settings.nil? && !current_settings['slaves'].nil?
747
- replicas = index_settings.delete(:replicas) ||
748
- index_settings.delete('replicas') ||
749
- index_settings.delete(:slaves) ||
750
- index_settings.delete('slaves')
751
- index_settings[used_slaves ? :slaves : :replicas] = replicas unless replicas.nil? || options[:inherit]
752
- @algolia_indexes[settings].set_settings(index_settings)
796
+ options[:check_settings] = true if options[:check_settings].nil?
797
+
798
+ current_settings = if options[:check_settings] && !algolia_indexing_disabled?(options)
799
+ @algolia_indexes[settings].get_settings(:getVersion => 1) rescue nil # if the index doesn't exist
800
+ end
801
+
802
+ if !algolia_indexing_disabled?(options) && options[:check_settings] && algoliasearch_settings_changed?(current_settings, index_settings)
803
+ set_settings_method = options[:synchronous] ? :set_settings! : :set_settings
804
+ @algolia_indexes[settings].send(set_settings_method, index_settings)
753
805
  end
754
806
 
755
807
  @algolia_indexes[settings]
@@ -785,8 +837,8 @@ module AlgoliaSearch
785
837
  end
786
838
 
787
839
  def algolia_object_id_changed?(o, options = nil)
788
- m = "#{algolia_object_id_method(options)}_changed?"
789
- o.respond_to?(m) ? o.send(m) : false
840
+ changed = algolia_attribute_changed?(o, algolia_object_id_method(options))
841
+ changed.nil? ? false : changed
790
842
  end
791
843
 
792
844
  def algoliasearch_settings_changed?(prev, current)
@@ -796,6 +848,8 @@ module AlgoliaSearch
796
848
  if v.is_a?(Array) and prev_v.is_a?(Array)
797
849
  # compare array of strings, avoiding symbols VS strings comparison
798
850
  return true if v.map { |x| x.to_s } != prev_v.map { |x| x.to_s }
851
+ elsif v.blank? # blank? check is needed to compare [] and null
852
+ return true unless prev_v.blank?
799
853
  else
800
854
  return true if prev_v != v
801
855
  end
@@ -864,7 +918,7 @@ module AlgoliaSearch
864
918
  def algolia_find_in_batches(batch_size, &block)
865
919
  if (defined?(::ActiveRecord) && ancestors.include?(::ActiveRecord::Base)) || respond_to?(:find_in_batches)
866
920
  find_in_batches(:batch_size => batch_size, &block)
867
- elsif defined?(::Sequel) && self < Sequel::Model
921
+ elsif defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
868
922
  dataset.extension(:pagination).each_page(batch_size, &block)
869
923
  else
870
924
  # don't worry, mongoid has its own underlying cursor/streaming mechanism
@@ -879,6 +933,50 @@ module AlgoliaSearch
879
933
  yield items unless items.empty?
880
934
  end
881
935
  end
936
+
937
+ def algolia_attribute_changed?(object, attr_name)
938
+ # if one of two method is implemented, we return its result
939
+ # true/false means whether it has changed or not
940
+ # +#{attr_name}_changed?+ always defined for automatic attributes but deprecated after Rails 5.2
941
+ # +will_save_change_to_#{attr_name}?+ should be use instead for Rails 5.2+, also defined for automatic attributes.
942
+ # If none of the method are defined, it's a dynamic attribute
943
+
944
+ method_name = "#{attr_name}_changed?"
945
+ if object.respond_to?(method_name)
946
+ # If +#{attr_name}_changed?+ respond we want to see if the method is user defined or if it's automatically
947
+ # defined by Rails.
948
+ # If it's user-defined, we call it.
949
+ # If it's automatic we check ActiveRecord version to see if this method is deprecated
950
+ # and try to call +will_save_change_to_#{attr_name}?+ instead.
951
+ # See: https://github.com/algolia/algoliasearch-rails/pull/338
952
+ # This feature is not compatible with Ruby 1.8
953
+ # In this case, we always call #{attr_name}_changed?
954
+ if Object.const_defined?(:RUBY_VERSION) && RUBY_VERSION.to_f < 1.9
955
+ return object.send(method_name)
956
+ end
957
+ unless automatic_changed_method?(object, method_name) && automatic_changed_method_deprecated?
958
+ return object.send(method_name)
959
+ end
960
+ end
961
+
962
+ if object.respond_to?("will_save_change_to_#{attr_name}?")
963
+ return object.send("will_save_change_to_#{attr_name}?")
964
+ end
965
+
966
+ # We don't know if the attribute has changed, so conservatively assume it has
967
+ true
968
+ end
969
+
970
+ def automatic_changed_method?(object, method_name)
971
+ raise ArgumentError.new("Method #{method_name} doesn't exist on #{object.class.name}") unless object.respond_to?(method_name)
972
+ file = object.method(method_name).source_location[0]
973
+ file.end_with?("active_model/attribute_methods.rb")
974
+ end
975
+
976
+ def automatic_changed_method_deprecated?
977
+ (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 1) ||
978
+ (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR > 5)
979
+ end
882
980
  end
883
981
 
884
982
  # these are the instance methods included
@@ -930,8 +1028,10 @@ module AlgoliaSearch
930
1028
  end
931
1029
 
932
1030
  def algolia_mark_must_reindex
933
- @algolia_must_reindex =
934
- if defined?(::Sequel) && is_a?(Sequel::Model)
1031
+ # algolia_must_reindex flag is reset after every commit as part. If we must reindex at any point in
1032
+ # a stransaction, keep flag set until it is explicitly unset
1033
+ @algolia_must_reindex ||=
1034
+ if defined?(::Sequel) && defined?(::Sequel::Model) && is_a?(Sequel::Model)
935
1035
  new? || self.class.algolia_must_reindex?(self)
936
1036
  else
937
1037
  new_record? || self.class.algolia_must_reindex?(self)