algoliasearch-rails 1.19.1 → 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.
- checksums.yaml +5 -5
- data/{ChangeLog → CHANGELOG.MD} +194 -1
- data/Gemfile +14 -37
- data/Gemfile.lock +189 -164
- data/LICENSE +6 -7
- data/README.md +143 -53
- data/algoliasearch-rails.gemspec +10 -10
- data/lib/algoliasearch/configuration.rb +28 -2
- data/lib/algoliasearch/pagination/kaminari.rb +1 -1
- data/lib/algoliasearch/tasks/algoliasearch.rake +6 -1
- data/lib/algoliasearch/utilities.rb +19 -4
- data/lib/algoliasearch/version.rb +3 -0
- data/lib/algoliasearch-rails.rb +236 -134
- data/spec/spec_helper.rb +20 -4
- metadata +14 -30
- data/.travis.yml +0 -51
- data/VERSION +0 -1
data/lib/algoliasearch-rails.rb
CHANGED
@@ -1,13 +1,6 @@
|
|
1
|
-
|
2
|
-
require "rubygems"
|
3
|
-
require "bundler"
|
4
|
-
|
5
|
-
Bundler.setup :default
|
6
|
-
rescue => e
|
7
|
-
puts "AlgoliaSearch: #{e.message}"
|
8
|
-
end
|
9
|
-
require 'algoliasearch'
|
1
|
+
require 'algolia'
|
10
2
|
|
3
|
+
require 'algoliasearch/version'
|
11
4
|
require 'algoliasearch/utilities'
|
12
5
|
|
13
6
|
if defined? Rails
|
@@ -54,33 +47,60 @@ module AlgoliaSearch
|
|
54
47
|
end
|
55
48
|
|
56
49
|
class IndexSettings
|
50
|
+
DEFAULT_BATCH_SIZE = 1000
|
57
51
|
|
58
52
|
# AlgoliaSearch settings
|
59
|
-
OPTIONS = [
|
60
|
-
|
61
|
-
:
|
62
|
-
|
63
|
-
:ranking, :customRanking, :
|
64
|
-
|
65
|
-
:
|
66
|
-
|
67
|
-
:
|
68
|
-
:
|
69
|
-
|
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
|
+
]
|
70
85
|
OPTIONS.each do |k|
|
71
86
|
define_method k do |v|
|
72
87
|
instance_variable_set("@#{k}", v)
|
73
88
|
end
|
74
89
|
end
|
75
90
|
|
76
|
-
def initialize(options, block)
|
91
|
+
def initialize(options, &block)
|
77
92
|
@options = options
|
78
|
-
instance_exec(&block) if
|
93
|
+
instance_exec(&block) if block_given?
|
94
|
+
end
|
95
|
+
|
96
|
+
def use_serializer(serializer)
|
97
|
+
@serializer = serializer
|
98
|
+
# instance_variable_set("@serializer", serializer)
|
79
99
|
end
|
80
100
|
|
81
101
|
def attribute(*names, &block)
|
82
102
|
raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
|
83
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
103
|
+
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
|
84
104
|
@attributes ||= {}
|
85
105
|
names.flatten.each do |name|
|
86
106
|
@attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
|
@@ -90,7 +110,7 @@ module AlgoliaSearch
|
|
90
110
|
|
91
111
|
def add_attribute(*names, &block)
|
92
112
|
raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1
|
93
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
113
|
+
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
|
94
114
|
@additional_attributes ||= {}
|
95
115
|
names.each do |name|
|
96
116
|
@additional_attributes[name.to_s] = block_given? ? Proc.new { |o| o.instance_eval(&block) } : Proc.new { |o| o.send(name) }
|
@@ -103,7 +123,7 @@ module AlgoliaSearch
|
|
103
123
|
end
|
104
124
|
|
105
125
|
def is_sequel?(object)
|
106
|
-
defined?(::Sequel) && object.class < ::Sequel::Model
|
126
|
+
defined?(::Sequel) && defined?(::Sequel::Model) && object.class < ::Sequel::Model
|
107
127
|
end
|
108
128
|
|
109
129
|
def is_active_record?(object)
|
@@ -124,15 +144,7 @@ module AlgoliaSearch
|
|
124
144
|
end
|
125
145
|
|
126
146
|
def get_attribute_names(object)
|
127
|
-
|
128
|
-
get_default_attributes(object).keys
|
129
|
-
else
|
130
|
-
@attributes.keys
|
131
|
-
end
|
132
|
-
|
133
|
-
res += @additional_attributes.keys if @additional_attributes
|
134
|
-
|
135
|
-
res
|
147
|
+
get_attributes(object).keys
|
136
148
|
end
|
137
149
|
|
138
150
|
def attributes_to_hash(attributes, object)
|
@@ -144,19 +156,27 @@ module AlgoliaSearch
|
|
144
156
|
end
|
145
157
|
|
146
158
|
def get_attributes(object)
|
147
|
-
|
148
|
-
|
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
|
149
163
|
else
|
150
|
-
if
|
151
|
-
|
152
|
-
|
153
|
-
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)
|
154
167
|
else
|
155
|
-
|
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
|
156
176
|
end
|
157
177
|
end
|
158
178
|
|
159
|
-
attributes.merge!(attributes_to_hash(@additional_attributes, object))
|
179
|
+
attributes.merge!(attributes_to_hash(@additional_attributes, object)) if @additional_attributes
|
160
180
|
|
161
181
|
if @options[:sanitize]
|
162
182
|
sanitizer = begin
|
@@ -191,7 +211,7 @@ module AlgoliaSearch
|
|
191
211
|
def encode_attributes(v)
|
192
212
|
case v
|
193
213
|
when String
|
194
|
-
v.force_encoding('utf-8')
|
214
|
+
v.dup.force_encoding('utf-8')
|
195
215
|
when Hash
|
196
216
|
v.each { |key, value| v[key] = encode_attributes(value) }
|
197
217
|
when Array
|
@@ -201,15 +221,15 @@ module AlgoliaSearch
|
|
201
221
|
end
|
202
222
|
end
|
203
223
|
|
204
|
-
def geoloc(lat_attr, lng_attr)
|
205
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
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]
|
206
226
|
add_attribute :_geoloc do |o|
|
207
|
-
{ :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 }
|
208
228
|
end
|
209
229
|
end
|
210
230
|
|
211
231
|
def tags(*args, &block)
|
212
|
-
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:
|
232
|
+
raise ArgumentError.new('Cannot specify additional attributes on a replica index') if @options[:replica]
|
213
233
|
add_attribute :_tags do |o|
|
214
234
|
v = block_given? ? o.instance_eval(&block) : args
|
215
235
|
v.is_a?(Array) ? v : [v]
|
@@ -226,16 +246,12 @@ module AlgoliaSearch
|
|
226
246
|
v = get_setting(k)
|
227
247
|
settings[k] = v if !v.nil?
|
228
248
|
end
|
229
|
-
|
230
|
-
|
231
|
-
name = opts[:index_name]
|
232
|
-
name = "#{name}_#{Rails.env.to_s}" if opts[:per_environment]
|
233
|
-
name
|
234
|
-
end
|
235
|
-
settings.delete(:slaves) if settings[:slaves].empty?
|
249
|
+
|
250
|
+
if !@options[:replica]
|
236
251
|
settings[:replicas] = additional_indexes.select { |opts, s| opts[:replica] }.map do |opts, s|
|
237
252
|
name = opts[:index_name]
|
238
253
|
name = "#{name}_#{Rails.env.to_s}" if opts[:per_environment]
|
254
|
+
name = "virtual(#{name})" if opts[:virtual]
|
239
255
|
name
|
240
256
|
end
|
241
257
|
settings.delete(:replicas) if settings[:replicas].empty?
|
@@ -244,27 +260,20 @@ module AlgoliaSearch
|
|
244
260
|
end
|
245
261
|
|
246
262
|
def add_index(index_name, options = {}, &block)
|
247
|
-
raise ArgumentError.new('Cannot specify additional index on a replica index') if @options[:
|
263
|
+
raise ArgumentError.new('Cannot specify additional index on a replica index') if @options[:replica]
|
248
264
|
raise ArgumentError.new('No block given') if !block_given?
|
249
265
|
raise ArgumentError.new('Options auto_index and auto_remove cannot be set on nested indexes') if options[:auto_index] || options[:auto_remove]
|
250
266
|
@additional_indexes ||= {}
|
251
|
-
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] })
|
252
267
|
options[:index_name] = index_name
|
253
|
-
@additional_indexes[options] = IndexSettings.new(options,
|
268
|
+
@additional_indexes[options] = IndexSettings.new(options, &block)
|
254
269
|
end
|
255
270
|
|
256
271
|
def add_replica(index_name, options = {}, &block)
|
257
|
-
raise ArgumentError.new('Cannot specify additional replicas on a replica index') if @options[:
|
272
|
+
raise ArgumentError.new('Cannot specify additional replicas on a replica index') if @options[:replica]
|
258
273
|
raise ArgumentError.new('No block given') if !block_given?
|
259
274
|
add_index(index_name, options.merge({ :replica => true, :primary_settings => self }), &block)
|
260
275
|
end
|
261
276
|
|
262
|
-
def add_slave(index_name, options = {}, &block)
|
263
|
-
raise ArgumentError.new('Cannot specify additional slaves on a slave index') if @options[:slave] || @options[:replica]
|
264
|
-
raise ArgumentError.new('No block given') if !block_given?
|
265
|
-
add_index(index_name, options.merge({ :slave => true, :primary_settings => self }), &block)
|
266
|
-
end
|
267
|
-
|
268
277
|
def additional_indexes
|
269
278
|
@additional_indexes || {}
|
270
279
|
end
|
@@ -282,11 +291,11 @@ module AlgoliaSearch
|
|
282
291
|
# are correctly logged or thrown depending on the `raise_on_failure` option
|
283
292
|
class SafeIndex
|
284
293
|
def initialize(name, raise_on_failure)
|
285
|
-
@index =
|
294
|
+
@index = AlgoliaSearch.client.init_index(name)
|
286
295
|
@raise_on_failure = raise_on_failure.nil? || raise_on_failure
|
287
296
|
end
|
288
297
|
|
289
|
-
::Algolia::Index.instance_methods(false).each do |m|
|
298
|
+
::Algolia::Search::Index.instance_methods(false).each do |m|
|
290
299
|
define_method(m) do |*args, &block|
|
291
300
|
SafeIndex.log_or_throw(m, @raise_on_failure) do
|
292
301
|
@index.send(m, *args, &block)
|
@@ -307,7 +316,7 @@ module AlgoliaSearch
|
|
307
316
|
SafeIndex.log_or_throw(:get_settings, @raise_on_failure) do
|
308
317
|
begin
|
309
318
|
@index.get_settings(*args)
|
310
|
-
rescue Algolia::
|
319
|
+
rescue Algolia::AlgoliaHttpError => e
|
311
320
|
return {} if e.code == 404 # not fatal
|
312
321
|
raise e
|
313
322
|
end
|
@@ -317,7 +326,7 @@ module AlgoliaSearch
|
|
317
326
|
# expose move as well
|
318
327
|
def self.move_index(old_name, new_name)
|
319
328
|
SafeIndex.log_or_throw(:move_index, true) do
|
320
|
-
|
329
|
+
AlgoliaSearch.client.move_index(old_name, new_name)
|
321
330
|
end
|
322
331
|
end
|
323
332
|
|
@@ -367,13 +376,13 @@ module AlgoliaSearch
|
|
367
376
|
end
|
368
377
|
|
369
378
|
def algoliasearch(options = {}, &block)
|
370
|
-
self.algoliasearch_settings = IndexSettings.new(options,
|
379
|
+
self.algoliasearch_settings = IndexSettings.new(options, &block)
|
371
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)
|
372
381
|
|
373
382
|
attr_accessor :highlight_result, :snippet_result
|
374
383
|
|
375
384
|
if options[:synchronous] == true
|
376
|
-
if defined?(::Sequel) && self < Sequel::Model
|
385
|
+
if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
377
386
|
class_eval do
|
378
387
|
copy_after_validation = instance_method(:after_validation)
|
379
388
|
define_method(:after_validation) do |*args|
|
@@ -400,15 +409,14 @@ module AlgoliaSearch
|
|
400
409
|
raise ArgumentError.new("Invalid `enqueue` option: #{options[:enqueue]}")
|
401
410
|
end
|
402
411
|
algoliasearch_options[:enqueue] = Proc.new do |record, remove|
|
403
|
-
proc.call(record, remove) unless
|
412
|
+
proc.call(record, remove) unless algolia_without_auto_index_scope
|
404
413
|
end
|
405
414
|
end
|
406
415
|
unless options[:auto_index] == false
|
407
|
-
if defined?(::Sequel) && self < Sequel::Model
|
416
|
+
if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
408
417
|
class_eval do
|
409
418
|
copy_after_validation = instance_method(:after_validation)
|
410
419
|
copy_before_save = instance_method(:before_save)
|
411
|
-
copy_after_commit = instance_method(:after_commit)
|
412
420
|
|
413
421
|
define_method(:after_validation) do |*args|
|
414
422
|
super(*args)
|
@@ -422,10 +430,23 @@ module AlgoliaSearch
|
|
422
430
|
super(*args)
|
423
431
|
end
|
424
432
|
|
425
|
-
|
426
|
-
|
427
|
-
copy_after_commit
|
428
|
-
|
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
|
429
450
|
end
|
430
451
|
end
|
431
452
|
else
|
@@ -439,7 +460,7 @@ module AlgoliaSearch
|
|
439
460
|
end
|
440
461
|
end
|
441
462
|
unless options[:auto_remove] == false
|
442
|
-
if defined?(::Sequel) && self < Sequel::Model
|
463
|
+
if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
443
464
|
class_eval do
|
444
465
|
copy_after_destroy = instance_method(:after_destroy)
|
445
466
|
|
@@ -456,20 +477,28 @@ module AlgoliaSearch
|
|
456
477
|
end
|
457
478
|
|
458
479
|
def algolia_without_auto_index(&block)
|
459
|
-
|
480
|
+
self.algolia_without_auto_index_scope = true
|
460
481
|
begin
|
461
482
|
yield
|
462
483
|
ensure
|
463
|
-
|
484
|
+
self.algolia_without_auto_index_scope = false
|
464
485
|
end
|
465
486
|
end
|
466
487
|
|
467
|
-
def
|
468
|
-
|
488
|
+
def algolia_without_auto_index_scope=(value)
|
489
|
+
Thread.current["algolia_without_auto_index_scope_for_#{self.model_name}"] = value
|
490
|
+
end
|
491
|
+
|
492
|
+
def algolia_without_auto_index_scope
|
493
|
+
Thread.current["algolia_without_auto_index_scope_for_#{self.model_name}"]
|
494
|
+
end
|
495
|
+
|
496
|
+
def algolia_reindex!(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
|
497
|
+
return if algolia_without_auto_index_scope
|
469
498
|
algolia_configurations.each do |options, settings|
|
470
499
|
next if algolia_indexing_disabled?(options)
|
471
500
|
index = algolia_ensure_init(options, settings)
|
472
|
-
next if options[:
|
501
|
+
next if options[:replica]
|
473
502
|
last_task = nil
|
474
503
|
|
475
504
|
algolia_find_in_batches(batch_size) do |group|
|
@@ -489,38 +518,44 @@ module AlgoliaSearch
|
|
489
518
|
end
|
490
519
|
last_task = index.save_objects(objects)
|
491
520
|
end
|
492
|
-
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])
|
493
522
|
end
|
494
523
|
nil
|
495
524
|
end
|
496
525
|
|
497
526
|
# reindex whole database using a extra temporary index + move operation
|
498
|
-
def algolia_reindex(batch_size =
|
499
|
-
return if
|
527
|
+
def algolia_reindex(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
|
528
|
+
return if algolia_without_auto_index_scope
|
500
529
|
algolia_configurations.each do |options, settings|
|
501
530
|
next if algolia_indexing_disabled?(options)
|
502
|
-
next if options[:
|
531
|
+
next if options[:replica]
|
503
532
|
|
504
533
|
# fetch the master settings
|
505
534
|
master_index = algolia_ensure_init(options, settings)
|
506
535
|
master_settings = master_index.get_settings rescue {} # if master doesn't exist yet
|
536
|
+
master_exists = master_settings != {}
|
507
537
|
master_settings.merge!(JSON.parse(settings.to_settings.to_json)) # convert symbols to strings
|
508
538
|
|
509
539
|
# remove the replicas of the temporary index
|
510
|
-
master_settings.delete :slaves
|
511
|
-
master_settings.delete 'slaves'
|
512
540
|
master_settings.delete :replicas
|
513
541
|
master_settings.delete 'replicas'
|
514
542
|
|
515
543
|
# init temporary index
|
516
|
-
|
517
|
-
|
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 })
|
518
547
|
tmp_options.delete(:per_environment) # already included in the temporary index_name
|
519
548
|
tmp_settings = settings.dup
|
520
|
-
|
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
|
521
556
|
|
522
557
|
algolia_find_in_batches(batch_size) do |group|
|
523
|
-
if algolia_conditional_index?(
|
558
|
+
if algolia_conditional_index?(options)
|
524
559
|
# select only indexable objects
|
525
560
|
group = group.select { |o| algolia_indexable?(o, tmp_options) }
|
526
561
|
end
|
@@ -528,35 +563,52 @@ module AlgoliaSearch
|
|
528
563
|
tmp_index.save_objects(objects)
|
529
564
|
end
|
530
565
|
|
531
|
-
move_task = SafeIndex.move_index(tmp_index.name,
|
532
|
-
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]
|
533
568
|
end
|
534
569
|
nil
|
535
570
|
end
|
536
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
|
+
|
537
589
|
def algolia_index_objects(objects, synchronous = false)
|
538
590
|
algolia_configurations.each do |options, settings|
|
539
591
|
next if algolia_indexing_disabled?(options)
|
540
592
|
index = algolia_ensure_init(options, settings)
|
541
|
-
next if options[:
|
593
|
+
next if options[:replica]
|
542
594
|
task = index.save_objects(objects.map { |o| settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o, options) })
|
543
|
-
index.wait_task(task["taskID"]) if synchronous || options[:synchronous]
|
595
|
+
index.wait_task(task.raw_response["taskID"]) if synchronous || options[:synchronous]
|
544
596
|
end
|
545
597
|
end
|
546
598
|
|
547
599
|
def algolia_index!(object, synchronous = false)
|
548
|
-
return if
|
600
|
+
return if algolia_without_auto_index_scope
|
549
601
|
algolia_configurations.each do |options, settings|
|
550
602
|
next if algolia_indexing_disabled?(options)
|
551
603
|
object_id = algolia_object_id_of(object, options)
|
552
604
|
index = algolia_ensure_init(options, settings)
|
553
|
-
next if options[:
|
605
|
+
next if options[:replica]
|
554
606
|
if algolia_indexable?(object, options)
|
555
607
|
raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
|
556
608
|
if synchronous || options[:synchronous]
|
557
|
-
index.
|
609
|
+
index.save_object!(settings.get_attributes(object).merge 'objectID' => algolia_object_id_of(object, options))
|
558
610
|
else
|
559
|
-
index.
|
611
|
+
index.save_object(settings.get_attributes(object).merge 'objectID' => algolia_object_id_of(object, options))
|
560
612
|
end
|
561
613
|
elsif algolia_conditional_index?(options) && !object_id.blank?
|
562
614
|
# remove non-indexable objects
|
@@ -571,13 +623,13 @@ module AlgoliaSearch
|
|
571
623
|
end
|
572
624
|
|
573
625
|
def algolia_remove_from_index!(object, synchronous = false)
|
574
|
-
return if
|
626
|
+
return if algolia_without_auto_index_scope
|
575
627
|
object_id = algolia_object_id_of(object)
|
576
628
|
raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
|
577
629
|
algolia_configurations.each do |options, settings|
|
578
630
|
next if algolia_indexing_disabled?(options)
|
579
631
|
index = algolia_ensure_init(options, settings)
|
580
|
-
next if options[:
|
632
|
+
next if options[:replica]
|
581
633
|
if synchronous || options[:synchronous]
|
582
634
|
index.delete_object!(object_id)
|
583
635
|
else
|
@@ -591,8 +643,8 @@ module AlgoliaSearch
|
|
591
643
|
algolia_configurations.each do |options, settings|
|
592
644
|
next if algolia_indexing_disabled?(options)
|
593
645
|
index = algolia_ensure_init(options, settings)
|
594
|
-
next if options[:
|
595
|
-
synchronous || options[:synchronous] ? index.
|
646
|
+
next if options[:replica]
|
647
|
+
synchronous || options[:synchronous] ? index.clear_objects! : index.clear_objects
|
596
648
|
@algolia_indexes[settings] = nil
|
597
649
|
end
|
598
650
|
nil
|
@@ -601,8 +653,6 @@ module AlgoliaSearch
|
|
601
653
|
def algolia_raw_search(q, params = {})
|
602
654
|
index_name = params.delete(:index) ||
|
603
655
|
params.delete('index') ||
|
604
|
-
params.delete(:slave) ||
|
605
|
-
params.delete('slave') ||
|
606
656
|
params.delete(:replica) ||
|
607
657
|
params.delete('replica')
|
608
658
|
index = algolia_index(index_name)
|
@@ -648,7 +698,7 @@ module AlgoliaSearch
|
|
648
698
|
algolia_object_id_of(hit)
|
649
699
|
end
|
650
700
|
results = json['hits'].map do |hit|
|
651
|
-
o = results_by_id[hit['objectID']]
|
701
|
+
o = results_by_id[hit['objectID'].to_s]
|
652
702
|
if o
|
653
703
|
o.highlight_result = hit['_highlightResult']
|
654
704
|
o.snippet_result = hit['_snippetResult']
|
@@ -656,9 +706,9 @@ module AlgoliaSearch
|
|
656
706
|
end
|
657
707
|
end.compact
|
658
708
|
# Algolia has a default limit of 1000 retrievable hits
|
659
|
-
total_hits = json['nbHits'] < json['nbPages'] * json['hitsPerPage'] ?
|
660
|
-
json['nbHits']
|
661
|
-
res = AlgoliaSearch::Pagination.create(results, total_hits, algoliasearch_options.merge({ :page => json['page'] + 1, :per_page => json['hitsPerPage'] }))
|
709
|
+
total_hits = json['nbHits'].to_i < json['nbPages'].to_i * json['hitsPerPage'].to_i ?
|
710
|
+
json['nbHits'].to_i: json['nbPages'].to_i * json['hitsPerPage'].to_i
|
711
|
+
res = AlgoliaSearch::Pagination.create(results, total_hits, algoliasearch_options.merge({ :page => json['page'].to_i + 1, :per_page => json['hitsPerPage'] }))
|
662
712
|
res.extend(AdditionalMethods)
|
663
713
|
res.send(:algolia_init_raw_answer, json)
|
664
714
|
res
|
@@ -667,13 +717,11 @@ module AlgoliaSearch
|
|
667
717
|
def algolia_search_for_facet_values(facet, text, params = {})
|
668
718
|
index_name = params.delete(:index) ||
|
669
719
|
params.delete('index') ||
|
670
|
-
params.delete(:slave) ||
|
671
|
-
params.delete('slave') ||
|
672
720
|
params.delete(:replica) ||
|
673
721
|
params.delete('replicas')
|
674
722
|
index = algolia_index(index_name)
|
675
723
|
query = Hash[params.map { |k, v| [k.to_s, v.to_s] }]
|
676
|
-
index.
|
724
|
+
index.search_for_facet_values(facet, text, query)['facetHits']
|
677
725
|
end
|
678
726
|
|
679
727
|
# deprecated (renaming)
|
@@ -697,19 +745,22 @@ module AlgoliaSearch
|
|
697
745
|
end
|
698
746
|
|
699
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
|
700
751
|
algolia_configurations.each do |options, settings|
|
701
|
-
next if options
|
752
|
+
next if algolia_indexing_disabled?(options)
|
753
|
+
next if options[:replica]
|
702
754
|
return true if algolia_object_id_changed?(object, options)
|
703
755
|
settings.get_attribute_names(object).each do |k|
|
704
|
-
|
705
|
-
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)
|
706
758
|
end
|
707
759
|
[options[:if], options[:unless]].each do |condition|
|
708
760
|
case condition
|
709
761
|
when nil
|
710
762
|
when String, Symbol
|
711
|
-
|
712
|
-
return true if !object.respond_to?(changed_method) || object.send(changed_method)
|
763
|
+
return true if algolia_attribute_changed?(object, condition)
|
713
764
|
else
|
714
765
|
# if the :if, :unless condition is a anything else,
|
715
766
|
# we have no idea whether we should reindex or not
|
@@ -718,6 +769,7 @@ module AlgoliaSearch
|
|
718
769
|
end
|
719
770
|
end
|
720
771
|
end
|
772
|
+
# By default, we don't reindex
|
721
773
|
return false
|
722
774
|
end
|
723
775
|
|
@@ -735,19 +787,21 @@ module AlgoliaSearch
|
|
735
787
|
|
736
788
|
@algolia_indexes[settings] = SafeIndex.new(algolia_index_name(options), algoliasearch_options[:raise_on_failure])
|
737
789
|
|
738
|
-
current_settings = @algolia_indexes[settings].get_settings rescue nil # if the index doesn't exist
|
739
|
-
|
740
790
|
index_settings ||= settings.to_settings
|
741
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]
|
795
|
+
|
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
|
742
801
|
|
743
|
-
if !algolia_indexing_disabled?(options) &&
|
744
|
-
|
745
|
-
|
746
|
-
index_settings.delete('replicas') ||
|
747
|
-
index_settings.delete(:slaves) ||
|
748
|
-
index_settings.delete('slaves')
|
749
|
-
index_settings[used_slaves ? :slaves : :replicas] = replicas unless replicas.nil? || options[:inherit]
|
750
|
-
@algolia_indexes[settings].set_settings(index_settings)
|
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)
|
751
805
|
end
|
752
806
|
|
753
807
|
@algolia_indexes[settings]
|
@@ -783,8 +837,8 @@ module AlgoliaSearch
|
|
783
837
|
end
|
784
838
|
|
785
839
|
def algolia_object_id_changed?(o, options = nil)
|
786
|
-
|
787
|
-
|
840
|
+
changed = algolia_attribute_changed?(o, algolia_object_id_method(options))
|
841
|
+
changed.nil? ? false : changed
|
788
842
|
end
|
789
843
|
|
790
844
|
def algoliasearch_settings_changed?(prev, current)
|
@@ -794,6 +848,8 @@ module AlgoliaSearch
|
|
794
848
|
if v.is_a?(Array) and prev_v.is_a?(Array)
|
795
849
|
# compare array of strings, avoiding symbols VS strings comparison
|
796
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?
|
797
853
|
else
|
798
854
|
return true if prev_v != v
|
799
855
|
end
|
@@ -862,7 +918,7 @@ module AlgoliaSearch
|
|
862
918
|
def algolia_find_in_batches(batch_size, &block)
|
863
919
|
if (defined?(::ActiveRecord) && ancestors.include?(::ActiveRecord::Base)) || respond_to?(:find_in_batches)
|
864
920
|
find_in_batches(:batch_size => batch_size, &block)
|
865
|
-
elsif defined?(::Sequel) && self < Sequel::Model
|
921
|
+
elsif defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
|
866
922
|
dataset.extension(:pagination).each_page(batch_size, &block)
|
867
923
|
else
|
868
924
|
# don't worry, mongoid has its own underlying cursor/streaming mechanism
|
@@ -877,6 +933,50 @@ module AlgoliaSearch
|
|
877
933
|
yield items unless items.empty?
|
878
934
|
end
|
879
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
|
880
980
|
end
|
881
981
|
|
882
982
|
# these are the instance methods included
|
@@ -928,8 +1028,10 @@ module AlgoliaSearch
|
|
928
1028
|
end
|
929
1029
|
|
930
1030
|
def algolia_mark_must_reindex
|
931
|
-
|
932
|
-
|
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)
|
933
1035
|
new? || self.class.algolia_must_reindex?(self)
|
934
1036
|
else
|
935
1037
|
new_record? || self.class.algolia_must_reindex?(self)
|