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.
- checksums.yaml +5 -5
- data/.travis.yml +56 -42
- data/{ChangeLog → CHANGELOG.MD} +163 -1
- data/Gemfile +25 -6
- data/Gemfile.lock +144 -325
- data/LICENSE +6 -7
- data/README.md +448 -306
- data/algoliasearch-rails.gemspec +10 -8
- data/lib/algoliasearch-rails.rb +285 -117
- data/lib/algoliasearch/configuration.rb +3 -1
- data/lib/algoliasearch/tasks/algoliasearch.rake +6 -2
- data/lib/algoliasearch/utilities.rb +29 -3
- data/lib/algoliasearch/version.rb +3 -0
- data/spec/spec_helper.rb +26 -4
- data/vendor/assets/javascripts/algolia/algoliasearch.angular.js +23 -12
- data/vendor/assets/javascripts/algolia/algoliasearch.angular.min.js +2 -2
- data/vendor/assets/javascripts/algolia/algoliasearch.jquery.js +23 -12
- data/vendor/assets/javascripts/algolia/algoliasearch.jquery.min.js +2 -2
- data/vendor/assets/javascripts/algolia/algoliasearch.js +22 -12
- data/vendor/assets/javascripts/algolia/algoliasearch.min.js +2 -2
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.js +23 -12
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.min.js +2 -2
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.js +23 -12
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.min.js +2 -2
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.js +22 -12
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.min.js +2 -2
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.js +2020 -1301
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.min.js +3 -3
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.js +2019 -1300
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.min.js +3 -3
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.js +2003 -1284
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.min.js +3 -3
- metadata +19 -15
- data/VERSION +0 -1
data/algoliasearch-rails.gemspec
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
-
|
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
|
-
"
|
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
|
-
"
|
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>, ["
|
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>, ["
|
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>, ["
|
94
|
+
s.add_dependency(%q<algoliasearch>, [">= 1.26.0", "< 2.0.0"])
|
93
95
|
end
|
94
96
|
end
|
95
97
|
|
data/lib/algoliasearch-rails.rb
CHANGED
@@ -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 = [
|
59
|
-
|
60
|
-
:
|
61
|
-
:
|
62
|
-
|
63
|
-
:
|
64
|
-
|
65
|
-
:
|
66
|
-
|
67
|
-
:
|
68
|
-
:
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
147
|
-
|
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
|
150
|
-
|
151
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
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]
|
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(:
|
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.
|
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
|
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,
|
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
|
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
|
-
|
409
|
-
|
410
|
-
copy_after_commit
|
411
|
-
|
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
|
-
|
493
|
+
self.algolia_without_auto_index_scope = true
|
443
494
|
begin
|
444
495
|
yield
|
445
496
|
ensure
|
446
|
-
|
497
|
+
self.algolia_without_auto_index_scope = false
|
447
498
|
end
|
448
499
|
end
|
449
500
|
|
450
|
-
def
|
451
|
-
|
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
|
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 =
|
482
|
-
return if
|
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
|
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
|
-
|
498
|
-
|
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
|
-
|
504
|
-
|
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,
|
513
|
-
|
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
|
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
|
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
|
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) ||
|
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']
|
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) ||
|
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/
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
701
|
-
|
702
|
-
|
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
|
-
|
739
|
-
|
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
|
-
|
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
|