algoliasearch-rails 1.16.3 → 1.25.0

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.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +56 -42
  3. data/{ChangeLog → CHANGELOG.MD} +163 -1
  4. data/Gemfile +25 -6
  5. data/Gemfile.lock +144 -325
  6. data/LICENSE +6 -7
  7. data/README.md +448 -306
  8. data/algoliasearch-rails.gemspec +10 -8
  9. data/lib/algoliasearch-rails.rb +285 -117
  10. data/lib/algoliasearch/configuration.rb +3 -1
  11. data/lib/algoliasearch/tasks/algoliasearch.rake +6 -2
  12. data/lib/algoliasearch/utilities.rb +29 -3
  13. data/lib/algoliasearch/version.rb +3 -0
  14. data/spec/spec_helper.rb +26 -4
  15. data/vendor/assets/javascripts/algolia/algoliasearch.angular.js +23 -12
  16. data/vendor/assets/javascripts/algolia/algoliasearch.angular.min.js +2 -2
  17. data/vendor/assets/javascripts/algolia/algoliasearch.jquery.js +23 -12
  18. data/vendor/assets/javascripts/algolia/algoliasearch.jquery.min.js +2 -2
  19. data/vendor/assets/javascripts/algolia/algoliasearch.js +22 -12
  20. data/vendor/assets/javascripts/algolia/algoliasearch.min.js +2 -2
  21. data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.js +23 -12
  22. data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.min.js +2 -2
  23. data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.js +23 -12
  24. data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.min.js +2 -2
  25. data/vendor/assets/javascripts/algolia/v2/algoliasearch.js +22 -12
  26. data/vendor/assets/javascripts/algolia/v2/algoliasearch.min.js +2 -2
  27. data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.js +2020 -1301
  28. data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.min.js +3 -3
  29. data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.js +2019 -1300
  30. data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.min.js +3 -3
  31. data/vendor/assets/javascripts/algolia/v3/algoliasearch.js +2003 -1284
  32. data/vendor/assets/javascripts/algolia/v3/algoliasearch.min.js +3 -3
  33. metadata +19 -15
  34. data/VERSION +0 -1
@@ -1,10 +1,12 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
- VERSION = File.read(File.join(File.dirname(__FILE__), 'VERSION')).strip
3
+ require File.join(File.dirname(__FILE__), 'lib', 'algoliasearch', 'version')
4
+
5
+ require 'date'
4
6
 
5
7
  Gem::Specification.new do |s|
6
8
  s.name = "algoliasearch-rails"
7
- s.version = VERSION
9
+ s.version = AlgoliaSearch::VERSION
8
10
 
9
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
12
  s.authors = ["Algolia"]
@@ -12,7 +14,7 @@ Gem::Specification.new do |s|
12
14
  s.description = "AlgoliaSearch integration to your favorite ORM"
13
15
  s.email = "contact@algolia.com"
14
16
  s.extra_rdoc_files = [
15
- "ChangeLog",
17
+ "CHANGELOG.MD",
16
18
  "LICENSE",
17
19
  "README.md"
18
20
  ]
@@ -20,13 +22,12 @@ Gem::Specification.new do |s|
20
22
  ".document",
21
23
  ".rspec",
22
24
  ".travis.yml",
23
- "ChangeLog",
25
+ "CHANGELOG.MD",
24
26
  "Gemfile",
25
27
  "Gemfile.lock",
26
28
  "LICENSE",
27
29
  "README.md",
28
30
  "Rakefile",
29
- "VERSION",
30
31
  "algoliasearch-rails.gemspec",
31
32
  "lib/algoliasearch-rails.rb",
32
33
  "lib/algoliasearch/algolia_job.rb",
@@ -37,6 +38,7 @@ Gem::Specification.new do |s|
37
38
  "lib/algoliasearch/railtie.rb",
38
39
  "lib/algoliasearch/tasks/algoliasearch.rake",
39
40
  "lib/algoliasearch/utilities.rb",
41
+ "lib/algoliasearch/version.rb",
40
42
  "spec/spec_helper.rb",
41
43
  "spec/utilities_spec.rb",
42
44
  "vendor/assets/javascripts/algolia/algoliasearch.angular.js",
@@ -77,7 +79,7 @@ Gem::Specification.new do |s|
77
79
 
78
80
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
79
81
  s.add_runtime_dependency(%q<json>, [">= 1.5.1"])
80
- s.add_runtime_dependency(%q<algoliasearch>, ["~> 1.12.1"])
82
+ s.add_runtime_dependency(%q<algoliasearch>, [">= 1.26.0", "< 2.0.0"])
81
83
  s.add_development_dependency(%q<will_paginate>, [">= 2.3.15"])
82
84
  s.add_development_dependency(%q<kaminari>, [">= 0"])
83
85
  s.add_development_dependency "travis"
@@ -85,11 +87,11 @@ Gem::Specification.new do |s|
85
87
  s.add_development_dependency "rdoc"
86
88
  else
87
89
  s.add_dependency(%q<json>, [">= 1.5.1"])
88
- s.add_dependency(%q<algoliasearch>, ["~> 1.12.1"])
90
+ s.add_dependency(%q<algoliasearch>, [">= 1.26.0", "< 2.0.0"])
89
91
  end
90
92
  else
91
93
  s.add_dependency(%q<json>, [">= 1.5.1"])
92
- s.add_dependency(%q<algoliasearch>, ["~> 1.12.1"])
94
+ s.add_dependency(%q<algoliasearch>, [">= 1.26.0", "< 2.0.0"])
93
95
  end
94
96
  end
95
97
 
@@ -1,13 +1,6 @@
1
- begin
2
- require "rubygems"
3
- require "bundler"
4
-
5
- Bundler.setup :default
6
- rescue => e
7
- puts "AlgoliaSearch: #{e.message}"
8
- end
9
1
  require 'algoliasearch'
10
2
 
3
+ require 'algoliasearch/version'
11
4
  require 'algoliasearch/utilities'
12
5
 
13
6
  if defined? Rails
@@ -30,6 +23,7 @@ module AlgoliaSearch
30
23
  class NotConfigured < StandardError; end
31
24
  class BadConfiguration < StandardError; end
32
25
  class NoBlockGiven < StandardError; end
26
+ class MixedSlavesAndReplicas < StandardError; end
33
27
 
34
28
  autoload :Configuration, 'algoliasearch/configuration'
35
29
  extend Configuration
@@ -53,33 +47,62 @@ module AlgoliaSearch
53
47
  end
54
48
 
55
49
  class IndexSettings
50
+ DEFAULT_BATCH_SIZE = 1000
56
51
 
57
52
  # AlgoliaSearch settings
58
- OPTIONS = [:minWordSizefor1Typo, :minWordSizefor2Typos, :typoTolerance,
59
- :hitsPerPage, :attributesToRetrieve,
60
- :attributesToHighlight, :attributesToSnippet, :attributesToIndex,
61
- :highlightPreTag, :highlightPostTag,
62
- :ranking, :customRanking, :queryType, :attributesForFaceting,
63
- :separatorsToIndex, :optionalWords, :attributeForDistinct,
64
- :synonyms, :placeholders, :removeWordsIfNoResults, :replaceSynonymsInHighlight,
65
- :unretrievableAttributes, :disableTypoToleranceOnWords, :disableTypoToleranceOnAttributes, :altCorrections,
66
- :ignorePlurals, :maxValuesPerFacet, :distinct, :numericAttributesToIndex,
67
- :allowTyposOnNumericTokens, :allowCompressionOfIntegerArray,
68
- :advancedSyntax]
53
+ OPTIONS = [
54
+ # Attributes
55
+ :searchableAttributes, :attributesForFaceting, :unretrievableAttributes, :attributesToRetrieve,
56
+ :attributesToIndex, #Legacy name of searchableAttributes
57
+ # Ranking
58
+ :ranking, :customRanking, # Replicas are handled via `add_replica`
59
+ # Faceting
60
+ :maxValuesPerFacet, :sortFacetValuesBy,
61
+ # Highlighting / Snippeting
62
+ :attributesToHighlight, :attributesToSnippet, :highlightPreTag, :highlightPostTag,
63
+ :snippetEllipsisText, :restrictHighlightAndSnippetArrays,
64
+ # Pagination
65
+ :hitsPerPage, :paginationLimitedTo,
66
+ # Typo
67
+ :minWordSizefor1Typo, :minWordSizefor2Typos, :typoTolerance, :allowTyposOnNumericTokens,
68
+ :disableTypoToleranceOnAttributes, :disableTypoToleranceOnWords, :separatorsToIndex,
69
+ # Language
70
+ :ignorePlurals, :removeStopWords, :camelCaseAttributes, :decompoundedAttributes,
71
+ :keepDiacriticsOnCharacters, :queryLanguages, :indexLanguages,
72
+ # Query Rules
73
+ :enableRules,
74
+ # Query Strategy
75
+ :queryType, :removeWordsIfNoResults, :advancedSyntax, :optionalWords,
76
+ :disablePrefixOnAttributes, :disableExactOnAttributes, :exactOnSingleWordQuery, :alternativesAsExact,
77
+ # Performance
78
+ :numericAttributesForFiltering, :allowCompressionOfIntegerArray,
79
+ :numericAttributesToIndex, # Legacy name of numericAttributesForFiltering
80
+ # Advanced
81
+ :attributeForDistinct, :distinct, :replaceSynonymsInHighlight, :minProximity, :responseFields,
82
+ :maxFacetHits,
83
+
84
+ # Rails-specific
85
+ :synonyms, :placeholders, :altCorrections,
86
+ ]
69
87
  OPTIONS.each do |k|
70
88
  define_method k do |v|
71
89
  instance_variable_set("@#{k}", v)
72
90
  end
73
91
  end
74
92
 
75
- def initialize(options, block)
93
+ def initialize(options, &block)
76
94
  @options = options
77
- instance_exec(&block) if block
95
+ instance_exec(&block) if block_given?
96
+ end
97
+
98
+ def use_serializer(serializer)
99
+ @serializer = serializer
100
+ # instance_variable_set("@serializer", serializer)
78
101
  end
79
102
 
80
103
  def attribute(*names, &block)
81
104
  raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
82
- raise ArgumentError.new('Cannot specify additional attributes on a slave index') if @options[:slave]
105
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
83
106
  @attributes ||= {}
84
107
  names.flatten.each do |name|
85
108
  @attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
@@ -89,7 +112,7 @@ module AlgoliaSearch
89
112
 
90
113
  def add_attribute(*names, &block)
91
114
  raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
92
- raise ArgumentError.new('Cannot specify additional attributes on a slave index') if @options[:slave]
115
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
93
116
  @additional_attributes ||= {}
94
117
  names.each do |name|
95
118
  @additional_attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
@@ -123,15 +146,7 @@ module AlgoliaSearch
123
146
  end
124
147
 
125
148
  def get_attribute_names(object)
126
- res = if @attributes.nil? || @attributes.length == 0
127
- get_default_attributes(object).keys
128
- else
129
- @attributes.keys
130
- end
131
-
132
- res += @additional_attributes.keys if @additional_attributes
133
-
134
- res
149
+ get_attributes(object).keys
135
150
  end
136
151
 
137
152
  def attributes_to_hash(attributes, object)
@@ -143,19 +158,27 @@ module AlgoliaSearch
143
158
  end
144
159
 
145
160
  def get_attributes(object)
146
- attributes = if @attributes.nil? || @attributes.length == 0
147
- get_default_attributes(object)
161
+ # If a serializer is set, we ignore attributes
162
+ # everything should be done via the serializer
163
+ if not @serializer.nil?
164
+ attributes = @serializer.new(object).attributes
148
165
  else
149
- if is_active_record?(object)
150
- object.class.unscoped do
151
- attributes_to_hash(@attributes, object)
152
- end
166
+ if @attributes.nil? || @attributes.length == 0
167
+ # no `attribute ...` have been configured, use the default attributes of the model
168
+ attributes = get_default_attributes(object)
153
169
  else
154
- attributes_to_hash(@attributes, object)
170
+ # at least 1 `attribute ...` has been configured, therefore use ONLY the one configured
171
+ if is_active_record?(object)
172
+ object.class.unscoped do
173
+ attributes = attributes_to_hash(@attributes, object)
174
+ end
175
+ else
176
+ attributes = attributes_to_hash(@attributes, object)
177
+ end
155
178
  end
156
179
  end
157
180
 
158
- attributes.merge!(attributes_to_hash(@additional_attributes, object))
181
+ attributes.merge!(attributes_to_hash(@additional_attributes, object)) if @additional_attributes
159
182
 
160
183
  if @options[:sanitize]
161
184
  sanitizer = begin
@@ -200,15 +223,15 @@ module AlgoliaSearch
200
223
  end
201
224
  end
202
225
 
203
- def geoloc(lat_attr, lng_attr)
204
- raise ArgumentError.new('Cannot specify additional attributes on a slave index') if @options[:slave]
226
+ def geoloc(lat_attr = nil, lng_attr = nil, &block)
227
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
205
228
  add_attribute :_geoloc do |o|
206
- { :lat => o.send(lat_attr).to_f, :lng => o.send(lng_attr).to_f }
229
+ block_given? ? o.instance_eval(&block) : { :lat => o.send(lat_attr).to_f, :lng => o.send(lng_attr).to_f }
207
230
  end
208
231
  end
209
232
 
210
233
  def tags(*args, &block)
211
- raise ArgumentError.new('Cannot specify additional attributes on a slave index') if @options[:slave]
234
+ raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:slave] || @options[:replica]
212
235
  add_attribute :_tags do |o|
213
236
  v = block_given? ? o.instance_eval(&block) : args
214
237
  v.is_a?(Array) ? v : [v]
@@ -225,27 +248,43 @@ module AlgoliaSearch
225
248
  v = get_setting(k)
226
249
  settings[k] = v if !v.nil?
227
250
  end
228
- settings[:slaves] = additional_indexes.select { |options, s| options[:slave] }.map do |options, s|
229
- name = options[:index_name]
230
- name = "#{name}_#{Rails.env.to_s}" if options[:per_environment]
231
- name
232
- end if !@options[:slave]
251
+ if !@options[:slave] && !@options[:replica]
252
+ settings[:slaves] = additional_indexes.select { |opts, s| opts[:slave] }.map do |opts, s|
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?
258
+ settings[:replicas] = additional_indexes.select { |opts, s| opts[:replica] }.map do |opts, s|
259
+ name = opts[:index_name]
260
+ name = "#{name}_#{Rails.env.to_s}" if opts[:per_environment]
261
+ name
262
+ end
263
+ settings.delete(:replicas) if settings[:replicas].empty?
264
+ end
233
265
  settings
234
266
  end
235
267
 
236
268
  def add_index(index_name, options = {}, &block)
237
- raise ArgumentError.new('Cannot specify additional index on a slave index') if @options[:slave]
269
+ raise ArgumentError.new('Cannot specify additional index on a replica index') if @options[:slave] || @options[:replica]
238
270
  raise ArgumentError.new('No block given') if !block_given?
239
271
  raise ArgumentError.new('Options auto_index and auto_remove cannot be set on nested indexes') if options[:auto_index] || options[:auto_remove]
240
- options[:index_name] = index_name
241
272
  @additional_indexes ||= {}
242
- @additional_indexes[options] = IndexSettings.new(options, Proc.new)
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
+ options[:index_name] = index_name
275
+ @additional_indexes[options] = IndexSettings.new(options, &block)
276
+ end
277
+
278
+ def add_replica(index_name, options = {}, &block)
279
+ raise ArgumentError.new('Cannot specify additional replicas on a replica index') if @options[:slave] || @options[:replica]
280
+ raise ArgumentError.new('No block given') if !block_given?
281
+ add_index(index_name, options.merge({ :replica => true, :primary_settings => self }), &block)
243
282
  end
244
283
 
245
284
  def add_slave(index_name, options = {}, &block)
246
- raise ArgumentError.new('Cannot specify additional slaves on a slave index') if @options[:slave]
285
+ raise ArgumentError.new('Cannot specify additional slaves on a slave index') if @options[:slave] || @options[:replica]
247
286
  raise ArgumentError.new('No block given') if !block_given?
248
- add_index(index_name, options.merge({ :slave => true }), &block)
287
+ add_index(index_name, options.merge({ :slave => true, :primary_settings => self }), &block)
249
288
  end
250
289
 
251
290
  def additional_indexes
@@ -271,7 +310,7 @@ module AlgoliaSearch
271
310
 
272
311
  ::Algolia::Index.instance_methods(false).each do |m|
273
312
  define_method(m) do |*args, &block|
274
- SafeIndex.log_or_throw(m) do
313
+ SafeIndex.log_or_throw(m, @raise_on_failure) do
275
314
  @index.send(m, *args, &block)
276
315
  end
277
316
  end
@@ -280,18 +319,18 @@ module AlgoliaSearch
280
319
  # special handling of wait_task to handle null task_id
281
320
  def wait_task(task_id)
282
321
  return if task_id.nil? && !@raise_on_failure # ok
283
- SafeIndex.log_or_throw(:wait_task) do
322
+ SafeIndex.log_or_throw(:wait_task, @raise_on_failure) do
284
323
  @index.wait_task(task_id)
285
324
  end
286
325
  end
287
326
 
288
327
  # special handling of get_settings to avoid raising errors on 404
289
328
  def get_settings(*args)
290
- SafeIndex.log_or_throw(:move_index) do
329
+ SafeIndex.log_or_throw(:get_settings, @raise_on_failure) do
291
330
  begin
292
331
  @index.get_settings(*args)
293
332
  rescue Algolia::AlgoliaError => e
294
- return {} if e.status == 404 # not fatal
333
+ return {} if e.code == 404 # not fatal
295
334
  raise e
296
335
  end
297
336
  end
@@ -299,17 +338,17 @@ module AlgoliaSearch
299
338
 
300
339
  # expose move as well
301
340
  def self.move_index(old_name, new_name)
302
- SafeIndex.log_or_throw(:move_index) do
341
+ SafeIndex.log_or_throw(:move_index, true) do
303
342
  ::Algolia.move_index(old_name, new_name)
304
343
  end
305
344
  end
306
345
 
307
346
  private
308
- def self.log_or_throw(method, &block)
347
+ def self.log_or_throw(method, raise_on_failure, &block)
309
348
  begin
310
349
  yield
311
350
  rescue Algolia::AlgoliaError => e
312
- raise e if @raise_on_failure
351
+ raise e if raise_on_failure
313
352
  # log the error
314
353
  (Rails.logger || Logger.new(STDOUT)).error("[algoliasearch-rails] #{e.message}")
315
354
  # return something
@@ -350,7 +389,7 @@ module AlgoliaSearch
350
389
  end
351
390
 
352
391
  def algoliasearch(options = {}, &block)
353
- self.algoliasearch_settings = IndexSettings.new(options, block_given? ? Proc.new : nil)
392
+ self.algoliasearch_settings = IndexSettings.new(options, &block)
354
393
  self.algoliasearch_options = { :type => algolia_full_const_get(model_name.to_s), :per_page => algoliasearch_settings.get_setting(:hitsPerPage) || 10, :page => 1 }.merge(options)
355
394
 
356
395
  attr_accessor :highlight_result, :snippet_result
@@ -383,7 +422,7 @@ module AlgoliaSearch
383
422
  raise ArgumentError.new("Invalid `enqueue` option: #{options[:enqueue]}")
384
423
  end
385
424
  algoliasearch_options[:enqueue] = Proc.new do |record, remove|
386
- proc.call(record, remove) unless @algolia_without_auto_index_scope
425
+ proc.call(record, remove) unless algolia_without_auto_index_scope
387
426
  end
388
427
  end
389
428
  unless options[:auto_index] == false
@@ -391,7 +430,6 @@ module AlgoliaSearch
391
430
  class_eval do
392
431
  copy_after_validation = instance_method(:after_validation)
393
432
  copy_before_save = instance_method(:before_save)
394
- copy_after_commit = instance_method(:after_commit)
395
433
 
396
434
  define_method(:after_validation) do |*args|
397
435
  super(*args)
@@ -405,10 +443,23 @@ module AlgoliaSearch
405
443
  super(*args)
406
444
  end
407
445
 
408
- define_method(:after_commit) do |*args|
409
- super(*args)
410
- copy_after_commit.bind(self).call
411
- algolia_perform_index_tasks
446
+ sequel_version = Gem::Version.new(Sequel.version)
447
+ if sequel_version >= Gem::Version.new('4.0.0') && sequel_version < Gem::Version.new('5.0.0')
448
+ copy_after_commit = instance_method(:after_commit)
449
+ define_method(:after_commit) do |*args|
450
+ super(*args)
451
+ copy_after_commit.bind(self).call
452
+ algolia_perform_index_tasks
453
+ end
454
+ else
455
+ copy_after_save = instance_method(:after_save)
456
+ define_method(:after_save) do |*args|
457
+ super(*args)
458
+ copy_after_save.bind(self).call
459
+ self.db.after_commit do
460
+ algolia_perform_index_tasks
461
+ end
462
+ end
412
463
  end
413
464
  end
414
465
  else
@@ -439,20 +490,28 @@ module AlgoliaSearch
439
490
  end
440
491
 
441
492
  def algolia_without_auto_index(&block)
442
- @algolia_without_auto_index_scope = true
493
+ self.algolia_without_auto_index_scope = true
443
494
  begin
444
495
  yield
445
496
  ensure
446
- @algolia_without_auto_index_scope = false
497
+ self.algolia_without_auto_index_scope = false
447
498
  end
448
499
  end
449
500
 
450
- def algolia_reindex!(batch_size = 1000, synchronous = false)
451
- return if @algolia_without_auto_index_scope
501
+ def algolia_without_auto_index_scope=(value)
502
+ Thread.current["algolia_without_auto_index_scope_for_#{self.model_name}"] = value
503
+ end
504
+
505
+ def algolia_without_auto_index_scope
506
+ Thread.current["algolia_without_auto_index_scope_for_#{self.model_name}"]
507
+ end
508
+
509
+ def algolia_reindex!(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
510
+ return if algolia_without_auto_index_scope
452
511
  algolia_configurations.each do |options, settings|
453
512
  next if algolia_indexing_disabled?(options)
454
513
  index = algolia_ensure_init(options, settings)
455
- next if options[:slave]
514
+ next if options[:slave] || options[:replica]
456
515
  last_task = nil
457
516
 
458
517
  algolia_find_in_batches(batch_size) do |group|
@@ -472,36 +531,45 @@ module AlgoliaSearch
472
531
  end
473
532
  last_task = index.save_objects(objects)
474
533
  end
475
- index.wait_task(last_task["taskID"]) if last_task and synchronous == true
534
+ index.wait_task(last_task["taskID"]) if last_task and (synchronous || options[:synchronous])
476
535
  end
477
536
  nil
478
537
  end
479
538
 
480
539
  # reindex whole database using a extra temporary index + move operation
481
- def algolia_reindex(batch_size = 1000, synchronous = false)
482
- return if @algolia_without_auto_index_scope
540
+ def algolia_reindex(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
541
+ return if algolia_without_auto_index_scope
483
542
  algolia_configurations.each do |options, settings|
484
543
  next if algolia_indexing_disabled?(options)
485
- next if options[:slave]
544
+ next if options[:slave] || options[:replica]
486
545
 
487
546
  # fetch the master settings
488
547
  master_index = algolia_ensure_init(options, settings)
489
548
  master_settings = master_index.get_settings rescue {} # if master doesn't exist yet
490
549
  master_settings.merge!(JSON.parse(settings.to_settings.to_json)) # convert symbols to strings
491
550
 
492
- # remove the slaves of the temporary index
551
+ # remove the replicas of the temporary index
493
552
  master_settings.delete :slaves
494
553
  master_settings.delete 'slaves'
554
+ master_settings.delete :replicas
555
+ master_settings.delete 'replicas'
495
556
 
496
557
  # init temporary index
497
- index_name = algolia_index_name(options)
498
- tmp_options = options.merge({ :index_name => "#{index_name}.tmp" })
558
+ src_index_name = algolia_index_name(options)
559
+ tmp_index_name = "#{src_index_name}.tmp"
560
+ tmp_options = options.merge({ :index_name => tmp_index_name })
499
561
  tmp_options.delete(:per_environment) # already included in the temporary index_name
500
562
  tmp_settings = settings.dup
501
- tmp_index = algolia_ensure_init(tmp_options, tmp_settings, master_settings)
502
563
 
503
- algolia_find_in_batches(batch_size) do |group|
504
- if algolia_conditional_index?(tmp_options)
564
+ if options[:check_settings] == false
565
+ ::Algolia::copy_index!(src_index_name, tmp_index_name, %w(settings synonyms rules))
566
+ tmp_index = SafeIndex.new(tmp_index_name, !!options[:raise_on_failure])
567
+ else
568
+ tmp_index = algolia_ensure_init(tmp_options, tmp_settings, master_settings)
569
+ end
570
+
571
+ algolia_find_in_batches(batch_size) do |group|
572
+ if algolia_conditional_index?(options)
505
573
  # select only indexable objects
506
574
  group = group.select { |o| algolia_indexable?(o, tmp_options) }
507
575
  end
@@ -509,39 +577,58 @@ module AlgoliaSearch
509
577
  tmp_index.save_objects(objects)
510
578
  end
511
579
 
512
- move_task = SafeIndex.move_index(tmp_index.name, index_name)
513
- tmp_index.wait_task(move_task["taskID"]) if synchronous == true
580
+ move_task = SafeIndex.move_index(tmp_index.name, src_index_name)
581
+ master_index.wait_task(move_task["taskID"]) if synchronous || options[:synchronous]
514
582
  end
515
583
  nil
516
584
  end
517
585
 
586
+ def algolia_set_settings(synchronous = false)
587
+ algolia_configurations.each do |options, settings|
588
+ if options[:primary_settings] && options[:inherit]
589
+ primary = options[:primary_settings].to_settings
590
+ primary.delete :slaves
591
+ primary.delete 'slaves'
592
+ primary.delete :replicas
593
+ primary.delete 'replicas'
594
+ final_settings = primary.merge(settings.to_settings)
595
+ else
596
+ final_settings = settings.to_settings
597
+ end
598
+
599
+ index = SafeIndex.new(algolia_index_name(options), true)
600
+ task = index.set_settings(final_settings)
601
+ index.wait_task(task["taskID"]) if synchronous
602
+ end
603
+ end
604
+
518
605
  def algolia_index_objects(objects, synchronous = false)
519
606
  algolia_configurations.each do |options, settings|
520
607
  next if algolia_indexing_disabled?(options)
521
608
  index = algolia_ensure_init(options, settings)
522
- next if options[:slave]
609
+ next if options[:slave] || options[:replica]
523
610
  task = index.save_objects(objects.map { |o| settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o, options) })
524
- index.wait_task(task["taskID"]) if synchronous == true
611
+ index.wait_task(task["taskID"]) if synchronous || options[:synchronous]
525
612
  end
526
613
  end
527
614
 
528
615
  def algolia_index!(object, synchronous = false)
529
- return if @algolia_without_auto_index_scope
616
+ return if algolia_without_auto_index_scope
530
617
  algolia_configurations.each do |options, settings|
531
618
  next if algolia_indexing_disabled?(options)
532
619
  object_id = algolia_object_id_of(object, options)
533
620
  index = algolia_ensure_init(options, settings)
534
- next if options[:slave]
621
+ next if options[:slave] || options[:replica]
535
622
  if algolia_indexable?(object, options)
536
623
  raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
537
- if synchronous
624
+ if synchronous || options[:synchronous]
538
625
  index.add_object!(settings.get_attributes(object), object_id)
539
626
  else
540
627
  index.add_object(settings.get_attributes(object), object_id)
541
628
  end
542
629
  elsif algolia_conditional_index?(options) && !object_id.blank?
543
630
  # remove non-indexable objects
544
- if synchronous
631
+ if synchronous || options[:synchronous]
545
632
  index.delete_object!(object_id)
546
633
  else
547
634
  index.delete_object(object_id)
@@ -552,14 +639,14 @@ module AlgoliaSearch
552
639
  end
553
640
 
554
641
  def algolia_remove_from_index!(object, synchronous = false)
555
- return if @algolia_without_auto_index_scope
642
+ return if algolia_without_auto_index_scope
556
643
  object_id = algolia_object_id_of(object)
557
644
  raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
558
645
  algolia_configurations.each do |options, settings|
559
646
  next if algolia_indexing_disabled?(options)
560
647
  index = algolia_ensure_init(options, settings)
561
- next if options[:slave]
562
- if synchronous
648
+ next if options[:slave] || options[:replica]
649
+ if synchronous || options[:synchronous]
563
650
  index.delete_object!(object_id)
564
651
  else
565
652
  index.delete_object(object_id)
@@ -572,15 +659,20 @@ module AlgoliaSearch
572
659
  algolia_configurations.each do |options, settings|
573
660
  next if algolia_indexing_disabled?(options)
574
661
  index = algolia_ensure_init(options, settings)
575
- next if options[:slave]
576
- synchronous ? index.clear! : index.clear
662
+ next if options[:slave] || options[:replica]
663
+ synchronous || options[:synchronous] ? index.clear! : index.clear
577
664
  @algolia_indexes[settings] = nil
578
665
  end
579
666
  nil
580
667
  end
581
668
 
582
669
  def algolia_raw_search(q, params = {})
583
- index_name = params.delete(:index) || params.delete('index') || params.delete(:slave) || params.delete('slave')
670
+ index_name = params.delete(:index) ||
671
+ params.delete('index') ||
672
+ params.delete(:slave) ||
673
+ params.delete('slave') ||
674
+ params.delete(:replica) ||
675
+ params.delete('replica')
584
676
  index = algolia_index(index_name)
585
677
  index.search(q, Hash[params.map { |k,v| [k.to_s, v.to_s] }])
586
678
  end
@@ -624,7 +716,7 @@ module AlgoliaSearch
624
716
  algolia_object_id_of(hit)
625
717
  end
626
718
  results = json['hits'].map do |hit|
627
- o = results_by_id[hit['objectID']]
719
+ o = results_by_id[hit['objectID'].to_s]
628
720
  if o
629
721
  o.highlight_result = hit['_highlightResult']
630
722
  o.snippet_result = hit['_snippetResult']
@@ -632,16 +724,21 @@ module AlgoliaSearch
632
724
  end
633
725
  end.compact
634
726
  # Algolia has a default limit of 1000 retrievable hits
635
- total_hits = json['nbHits'] < json['nbPages'] * json['hitsPerPage'] ?
636
- json['nbHits'] : json['nbPages'] * json['hitsPerPage']
637
- res = AlgoliaSearch::Pagination.create(results, total_hits, algoliasearch_options.merge({ :page => json['page'] + 1, :per_page => json['hitsPerPage'] }))
727
+ total_hits = json['nbHits'].to_i < json['nbPages'].to_i * json['hitsPerPage'].to_i ?
728
+ json['nbHits'].to_i: json['nbPages'].to_i * json['hitsPerPage'].to_i
729
+ res = AlgoliaSearch::Pagination.create(results, total_hits, algoliasearch_options.merge({ :page => json['page'].to_i + 1, :per_page => json['hitsPerPage'] }))
638
730
  res.extend(AdditionalMethods)
639
731
  res.send(:algolia_init_raw_answer, json)
640
732
  res
641
733
  end
642
734
 
643
735
  def algolia_search_for_facet_values(facet, text, params = {})
644
- index_name = params.delete(:index) || params.delete('index') || params.delete(:slave) || params.delete('slave')
736
+ index_name = params.delete(:index) ||
737
+ params.delete('index') ||
738
+ params.delete(:slave) ||
739
+ params.delete('slave') ||
740
+ params.delete(:replica) ||
741
+ params.delete('replicas')
645
742
  index = algolia_index(index_name)
646
743
  query = Hash[params.map { |k, v| [k.to_s, v.to_s] }]
647
744
  index.search_facet(facet, text, query)['facetHits']
@@ -655,7 +752,7 @@ module AlgoliaSearch
655
752
  algolia_configurations.each do |o, s|
656
753
  return algolia_ensure_init(o, s) if o[:index_name].to_s == name.to_s
657
754
  end
658
- raise ArgumentError.new("Invalid index/slave name: #{name}")
755
+ raise ArgumentError.new("Invalid index/replica name: #{name}")
659
756
  end
660
757
  algolia_ensure_init
661
758
  end
@@ -668,23 +765,31 @@ module AlgoliaSearch
668
765
  end
669
766
 
670
767
  def algolia_must_reindex?(object)
768
+ # use +algolia_dirty?+ method if implemented
769
+ return object.send(:algolia_dirty?) if (object.respond_to?(:algolia_dirty?))
770
+ # Loop over each index to see if a attribute used in records has changed
671
771
  algolia_configurations.each do |options, settings|
672
- next if options[:slave]
772
+ next if algolia_indexing_disabled?(options)
773
+ next if options[:slave] || options[:replica]
673
774
  return true if algolia_object_id_changed?(object, options)
674
775
  settings.get_attribute_names(object).each do |k|
675
- changed_method = "#{k}_changed?"
676
- return true if !object.respond_to?(changed_method) || object.send(changed_method)
776
+ return true if algolia_attribute_changed?(object, k)
777
+ # return true if !object.respond_to?(changed_method) || object.send(changed_method)
677
778
  end
678
779
  [options[:if], options[:unless]].each do |condition|
679
780
  case condition
680
781
  when nil
681
782
  when String, Symbol
682
- changed_method = "#{condition}_changed?"
683
- return true if !object.respond_to?(changed_method) || object.send(changed_method)
783
+ return true if algolia_attribute_changed?(object, condition)
684
784
  else
785
+ # if the :if, :unless condition is a anything else,
786
+ # we have no idea whether we should reindex or not
787
+ # let's always reindex then
788
+ return true
685
789
  end
686
790
  end
687
791
  end
792
+ # By default, we don't reindex
688
793
  return false
689
794
  end
690
795
 
@@ -692,16 +797,33 @@ module AlgoliaSearch
692
797
 
693
798
  def algolia_ensure_init(options = nil, settings = nil, index_settings = nil)
694
799
  raise ArgumentError.new('No `algoliasearch` block found in your model.') if algoliasearch_settings.nil?
800
+
695
801
  @algolia_indexes ||= {}
802
+
696
803
  options ||= algoliasearch_options
697
804
  settings ||= algoliasearch_settings
805
+
698
806
  return @algolia_indexes[settings] if @algolia_indexes[settings]
807
+
699
808
  @algolia_indexes[settings] = SafeIndex.new(algolia_index_name(options), algoliasearch_options[:raise_on_failure])
700
- current_settings = @algolia_indexes[settings].get_settings rescue nil # if the index doesn't exist
701
- if !algolia_indexing_disabled?(options) && (index_settings || algoliasearch_settings_changed?(current_settings, settings.to_settings))
702
- index_settings ||= settings.to_settings
809
+
810
+ current_settings = @algolia_indexes[settings].get_settings(:getVersion => 1) rescue nil # if the index doesn't exist
811
+
812
+ index_settings ||= settings.to_settings
813
+ index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit]
814
+
815
+ options[:check_settings] = true if options[:check_settings].nil?
816
+
817
+ if !algolia_indexing_disabled?(options) && options[:check_settings] && algoliasearch_settings_changed?(current_settings, index_settings)
818
+ used_slaves = !current_settings.nil? && !current_settings['slaves'].nil?
819
+ replicas = index_settings.delete(:replicas) ||
820
+ index_settings.delete('replicas') ||
821
+ index_settings.delete(:slaves) ||
822
+ index_settings.delete('slaves')
823
+ index_settings[used_slaves ? :slaves : :replicas] = replicas unless replicas.nil? || options[:inherit]
703
824
  @algolia_indexes[settings].set_settings(index_settings)
704
825
  end
826
+
705
827
  @algolia_indexes[settings]
706
828
  end
707
829
 
@@ -735,8 +857,8 @@ module AlgoliaSearch
735
857
  end
736
858
 
737
859
  def algolia_object_id_changed?(o, options = nil)
738
- m = "#{algolia_object_id_method(options)}_changed?"
739
- o.respond_to?(m) ? o.send(m) : false
860
+ changed = algolia_attribute_changed?(o, algolia_object_id_method(options))
861
+ changed.nil? ? false : changed
740
862
  end
741
863
 
742
864
  def algoliasearch_settings_changed?(prev, current)
@@ -829,6 +951,50 @@ module AlgoliaSearch
829
951
  yield items unless items.empty?
830
952
  end
831
953
  end
954
+
955
+ def algolia_attribute_changed?(object, attr_name)
956
+ # if one of two method is implemented, we return its result
957
+ # true/false means whether it has changed or not
958
+ # +#{attr_name}_changed?+ always defined for automatic attributes but deprecated after Rails 5.2
959
+ # +will_save_change_to_#{attr_name}?+ should be use instead for Rails 5.2+, also defined for automatic attributes.
960
+ # If none of the method are defined, it's a dynamic attribute
961
+
962
+ method_name = "#{attr_name}_changed?"
963
+ if object.respond_to?(method_name)
964
+ # If +#{attr_name}_changed?+ respond we want to see if the method is user defined or if it's automatically
965
+ # defined by Rails.
966
+ # If it's user-defined, we call it.
967
+ # If it's automatic we check ActiveRecord version to see if this method is deprecated
968
+ # and try to call +will_save_change_to_#{attr_name}?+ instead.
969
+ # See: https://github.com/algolia/algoliasearch-rails/pull/338
970
+ # This feature is not compatible with Ruby 1.8
971
+ # In this case, we always call #{attr_name}_changed?
972
+ if Object.const_defined?(:RUBY_VERSION) && RUBY_VERSION.to_f < 1.9
973
+ return object.send(method_name)
974
+ end
975
+ unless automatic_changed_method?(object, method_name) && automatic_changed_method_deprecated?
976
+ return object.send(method_name)
977
+ end
978
+ end
979
+
980
+ if object.respond_to?("will_save_change_to_#{attr_name}?")
981
+ return object.send("will_save_change_to_#{attr_name}?")
982
+ end
983
+
984
+ # We don't know if the attribute has changed, so conservatively assume it has
985
+ true
986
+ end
987
+
988
+ def automatic_changed_method?(object, method_name)
989
+ raise ArgumentError.new("Method #{method_name} doesn't exist on #{object.class.name}") unless object.respond_to?(method_name)
990
+ file = object.method(method_name).source_location[0]
991
+ file.end_with?("active_model/attribute_methods.rb")
992
+ end
993
+
994
+ def automatic_changed_method_deprecated?
995
+ (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 1) ||
996
+ (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR > 5)
997
+ end
832
998
  end
833
999
 
834
1000
  # these are the instance methods included
@@ -851,15 +1017,15 @@ module AlgoliaSearch
851
1017
 
852
1018
  def algolia_enqueue_remove_from_index!(synchronous)
853
1019
  if algoliasearch_options[:enqueue]
854
- algoliasearch_options[:enqueue].call(self, true)
1020
+ algoliasearch_options[:enqueue].call(self, true) unless self.class.send(:algolia_indexing_disabled?, algoliasearch_options)
855
1021
  else
856
- algolia_remove_from_index!(synchronous)
1022
+ algolia_remove_from_index!(synchronous || algolia_synchronous?)
857
1023
  end
858
1024
  end
859
1025
 
860
1026
  def algolia_enqueue_index!(synchronous)
861
1027
  if algoliasearch_options[:enqueue]
862
- algoliasearch_options[:enqueue].call(self, false)
1028
+ algoliasearch_options[:enqueue].call(self, false) unless self.class.send(:algolia_indexing_disabled?, algoliasearch_options)
863
1029
  else
864
1030
  algolia_index!(synchronous)
865
1031
  end
@@ -880,7 +1046,9 @@ module AlgoliaSearch
880
1046
  end
881
1047
 
882
1048
  def algolia_mark_must_reindex
883
- @algolia_must_reindex =
1049
+ # algolia_must_reindex flag is reset after every commit as part. If we must reindex at any point in
1050
+ # a stransaction, keep flag set until it is explicitly unset
1051
+ @algolia_must_reindex ||=
884
1052
  if defined?(::Sequel) && is_a?(Sequel::Model)
885
1053
  new? || self.class.algolia_must_reindex?(self)
886
1054
  else