searchkick 1.2.1 → 1.3.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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +17 -5
- data/CHANGELOG.md +6 -0
- data/LICENSE.txt +1 -1
- data/README.md +26 -12
- data/lib/searchkick.rb +4 -0
- data/lib/searchkick/index.rb +62 -29
- data/lib/searchkick/model.rb +3 -1
- data/lib/searchkick/query.rb +334 -258
- data/lib/searchkick/version.rb +1 -1
- data/test/aggs_test.rb +4 -0
- data/test/ci/before_install.sh +6 -2
- data/test/facets_test.rb +1 -1
- data/test/gemfiles/apartment.gemfile +8 -0
- data/test/match_test.rb +8 -0
- data/test/multi_tenancy_test.rb +22 -0
- data/test/order_test.rb +6 -0
- data/test/sql_test.rb +5 -0
- data/test/test_helper.rb +48 -3
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9328dde4ac72c1fb914fdcbffe6a544e6f64b335
|
4
|
+
data.tar.gz: e59db37c34cc78f48139a8f6d604d2c4eb108d06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 181ac8dc2d783231b7696094583312ca73e4b1104f7a91af76cde94f30ffcd6c4d89bd88ace568fcf1825776ae348c8bb2c1e7b50061407251d6805fcf45281a
|
7
|
+
data.tar.gz: 786f655864dbdc1f9d978779000b6ae762be9ea476f832515cba1b46fa923c9dee8174bc92dff247580fdabc10a4d8aa4aaacb2690c0eea9d3fb3881e96e8d55
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -5,7 +5,7 @@ services:
|
|
5
5
|
- mongodb
|
6
6
|
before_install:
|
7
7
|
- ./test/ci/before_install.sh
|
8
|
-
script: bundle exec rake test
|
8
|
+
script: RUBYOPT=W0 bundle exec rake test
|
9
9
|
before_script:
|
10
10
|
- psql -c 'create database searchkick_test;' -U postgres
|
11
11
|
notifications:
|
@@ -22,10 +22,22 @@ gemfile:
|
|
22
22
|
- test/gemfiles/mongoid3.gemfile
|
23
23
|
- test/gemfiles/mongoid4.gemfile
|
24
24
|
- test/gemfiles/mongoid5.gemfile
|
25
|
+
env:
|
26
|
+
- ELASTICSEARCH_VERSION=2.3.0
|
25
27
|
matrix:
|
26
28
|
include:
|
27
|
-
- gemfile:
|
28
|
-
env:
|
29
|
+
- gemfile: Gemfile
|
30
|
+
env: ELASTICSEARCH_VERSION=1.0.0
|
31
|
+
- gemfile: Gemfile
|
32
|
+
env: ELASTICSEARCH_VERSION=1.7.0
|
33
|
+
- gemfile: Gemfile
|
34
|
+
env: ELASTICSEARCH_VERSION=2.0.0
|
35
|
+
- gemfile: Gemfile
|
36
|
+
env: ELASTICSEARCH_VERSION=5.0.0-alpha2
|
37
|
+
# - gemfile: test/gemfiles/nobrainer.gemfile
|
38
|
+
# env: NOBRAINER=true
|
29
39
|
allow_failures:
|
30
|
-
- gemfile:
|
31
|
-
env:
|
40
|
+
- gemfile: Gemfile
|
41
|
+
env: ELASTICSEARCH_VERSION=5.0.0-alpha2
|
42
|
+
# - gemfile: test/gemfiles/nobrainer.gemfile
|
43
|
+
# env: NOBRAINER=true
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -21,8 +21,6 @@ Plus:
|
|
21
21
|
- “Did you mean” suggestions
|
22
22
|
- works with ActiveRecord, Mongoid, and NoBrainer
|
23
23
|
|
24
|
-
**Searchkick 1.0 was just released!** See [instructions for upgrading](#100)
|
25
|
-
|
26
24
|
:speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][4]=true) for new features
|
27
25
|
|
28
26
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
@@ -247,6 +245,12 @@ Available options are:
|
|
247
245
|
User.search params[:q], fields: [{email: :exact}, :name]
|
248
246
|
```
|
249
247
|
|
248
|
+
### Phrase Matches
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
User.search "fresh honey", match: :phrase
|
252
|
+
```
|
253
|
+
|
250
254
|
### Language
|
251
255
|
|
252
256
|
Searchkick defaults to English for stemming. To change this, use:
|
@@ -329,7 +333,7 @@ gem 'gemoji-parser'
|
|
329
333
|
And use:
|
330
334
|
|
331
335
|
```ruby
|
332
|
-
Product.search "
|
336
|
+
Product.search "🍨🍰", emoji: true
|
333
337
|
```
|
334
338
|
|
335
339
|
### Indexing
|
@@ -633,6 +637,12 @@ price_ranges = [{to: 20}, {from: 20, to: 50}, {from: 50}]
|
|
633
637
|
Product.search "*", aggs: {price: {ranges: price_ranges}}
|
634
638
|
```
|
635
639
|
|
640
|
+
Minimum document count
|
641
|
+
|
642
|
+
```ruby
|
643
|
+
Product.search "apples", aggs: {store_id: {min_doc_count: 2}}
|
644
|
+
```
|
645
|
+
|
636
646
|
#### Moving From Facets
|
637
647
|
|
638
648
|
1. Replace `facets` with `aggs` in searches. **Note:** Stats facets are not supported at this time.
|
@@ -838,7 +848,7 @@ Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/b
|
|
838
848
|
class Business < ActiveRecord::Base
|
839
849
|
searchkick routing: true
|
840
850
|
|
841
|
-
def
|
851
|
+
def search_routing
|
842
852
|
city_id
|
843
853
|
end
|
844
854
|
end
|
@@ -919,20 +929,20 @@ Searchkick uses `ENV["ELASTICSEARCH_URL"]` for the Elasticsearch server. This d
|
|
919
929
|
|
920
930
|
### Heroku
|
921
931
|
|
922
|
-
Choose an add-on: [SearchBox](https://elements.heroku.com/addons/searchbox), [Bonsai](https://elements.heroku.com/addons/bonsai), or [
|
932
|
+
Choose an add-on: [SearchBox](https://elements.heroku.com/addons/searchbox), [Bonsai](https://elements.heroku.com/addons/bonsai), or [Elastic Cloud](https://elements.heroku.com/addons/foundelasticsearch).
|
923
933
|
|
924
934
|
```sh
|
925
935
|
# SearchBox
|
926
|
-
heroku addons:
|
927
|
-
heroku config:
|
936
|
+
heroku addons:create searchbox:starter
|
937
|
+
heroku config:set ELASTICSEARCH_URL=`heroku config:get SEARCHBOX_URL`
|
928
938
|
|
929
939
|
# Bonsai
|
930
|
-
heroku addons:
|
931
|
-
heroku config:
|
940
|
+
heroku addons:create bonsai
|
941
|
+
heroku config:set ELASTICSEARCH_URL=`heroku config:get BONSAI_URL`
|
932
942
|
|
933
943
|
# Found
|
934
|
-
heroku addons:
|
935
|
-
heroku config:
|
944
|
+
heroku addons:create foundelasticsearch
|
945
|
+
heroku config:set ELASTICSEARCH_URL=`heroku config:get FOUNDELASTICSEARCH_URL`
|
936
946
|
```
|
937
947
|
|
938
948
|
Then deploy and reindex:
|
@@ -1104,7 +1114,7 @@ Searchkick.multi_search([fresh_products, frozen_products])
|
|
1104
1114
|
|
1105
1115
|
Then use `fresh_products` and `frozen_products` as typical results.
|
1106
1116
|
|
1107
|
-
**Note:** Errors are not raised as with single requests. Use the `error` method on each query to check for errors. Also, the `below` option for misspellings
|
1117
|
+
**Note:** Errors are not raised as with single requests. Use the `error` method on each query to check for errors. Also, if you use the `below` option for misspellings, misspellings will be disabled.
|
1108
1118
|
|
1109
1119
|
## Reference
|
1110
1120
|
|
@@ -1316,6 +1326,10 @@ product.reindex # don't forget this
|
|
1316
1326
|
Product.searchkick_index.refresh # or this
|
1317
1327
|
```
|
1318
1328
|
|
1329
|
+
## Multi-Tenancy
|
1330
|
+
|
1331
|
+
Check out [this great post](https://www.tiagoamaro.com.br/2014/12/11/multi-tenancy-with-searchkick/) on the [Apartment](https://github.com/influitive/apartment) gem. Follow a similar pattern if you use another gem.
|
1332
|
+
|
1319
1333
|
## Migrating from Tire
|
1320
1334
|
|
1321
1335
|
1. Change `search` methods to `tire.search` and add index name in existing search calls
|
data/lib/searchkick.rb
CHANGED
@@ -58,6 +58,10 @@ module Searchkick
|
|
58
58
|
@server_version ||= client.info["version"]["number"]
|
59
59
|
end
|
60
60
|
|
61
|
+
def self.server_below?(version)
|
62
|
+
Gem::Version.new(server_version) < Gem::Version.new(version)
|
63
|
+
end
|
64
|
+
|
61
65
|
def self.enable_callbacks
|
62
66
|
self.callbacks_value = nil
|
63
67
|
end
|
data/lib/searchkick/index.rb
CHANGED
@@ -36,7 +36,7 @@ module Searchkick
|
|
36
36
|
begin
|
37
37
|
client.indices.get_alias(name: name).keys
|
38
38
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
39
|
-
|
39
|
+
{}
|
40
40
|
end
|
41
41
|
actions = old_indices.map { |old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
|
42
42
|
client.indices.update_aliases body: {actions: actions}
|
@@ -146,7 +146,7 @@ module Searchkick
|
|
146
146
|
begin
|
147
147
|
client.indices.get_aliases
|
148
148
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
149
|
-
|
149
|
+
{}
|
150
150
|
end
|
151
151
|
indices = all_indices.select { |k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
|
152
152
|
indices.each do |index|
|
@@ -218,6 +218,22 @@ module Searchkick
|
|
218
218
|
settings = options[:settings] || {}
|
219
219
|
mappings = options[:mappings]
|
220
220
|
else
|
221
|
+
below22 = Searchkick.server_below?("2.2.0")
|
222
|
+
below50 = Searchkick.server_below?("5.0.0-alpha1")
|
223
|
+
default_type = below50 ? "string" : "text"
|
224
|
+
default_analyzer = below50 ? :default_index : :default
|
225
|
+
keyword_mapping =
|
226
|
+
if below50
|
227
|
+
{
|
228
|
+
type: default_type,
|
229
|
+
index: "not_analyzed"
|
230
|
+
}
|
231
|
+
else
|
232
|
+
{
|
233
|
+
type: "keyword"
|
234
|
+
}
|
235
|
+
end
|
236
|
+
|
221
237
|
settings = {
|
222
238
|
analysis: {
|
223
239
|
analyzer: {
|
@@ -226,7 +242,7 @@ module Searchkick
|
|
226
242
|
tokenizer: "keyword",
|
227
243
|
filter: ["lowercase"] + (options[:stem_conversions] == false ? [] : ["searchkick_stemmer"])
|
228
244
|
},
|
229
|
-
|
245
|
+
default_analyzer => {
|
230
246
|
type: "custom",
|
231
247
|
# character filters -> tokenizer -> token filters
|
232
248
|
# https://www.elastic.co/guide/en/elasticsearch/guide/current/analysis-intro.html
|
@@ -380,8 +396,8 @@ module Searchkick
|
|
380
396
|
# - Only apply the synonym expansion at index time
|
381
397
|
# - Don't have the synonym filter applied search
|
382
398
|
# - Use directional synonyms where appropriate. You want to make sure that you're not injecting terms that are too general.
|
383
|
-
settings[:analysis][:analyzer][
|
384
|
-
settings[:analysis][:analyzer][
|
399
|
+
settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_synonym")
|
400
|
+
settings[:analysis][:analyzer][default_analyzer][:filter] << "searchkick_synonym"
|
385
401
|
|
386
402
|
%w(word_start word_middle word_end).each do |type|
|
387
403
|
settings[:analysis][:analyzer]["searchkick_#{type}_index".to_sym][:filter].insert(2, "searchkick_synonym")
|
@@ -395,8 +411,8 @@ module Searchkick
|
|
395
411
|
synonyms_path: Searchkick.wordnet_path
|
396
412
|
}
|
397
413
|
|
398
|
-
settings[:analysis][:analyzer][
|
399
|
-
settings[:analysis][:analyzer][
|
414
|
+
settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_wordnet")
|
415
|
+
settings[:analysis][:analyzer][default_analyzer][:filter] << "searchkick_wordnet"
|
400
416
|
|
401
417
|
%w(word_start word_middle word_end).each do |type|
|
402
418
|
settings[:analysis][:analyzer]["searchkick_#{type}_index".to_sym][:filter].insert(2, "searchkick_wordnet")
|
@@ -416,7 +432,7 @@ module Searchkick
|
|
416
432
|
mapping[conversions_field] = {
|
417
433
|
type: "nested",
|
418
434
|
properties: {
|
419
|
-
query: {type:
|
435
|
+
query: {type: default_type, analyzer: "searchkick_keyword"},
|
420
436
|
count: {type: "integer"}
|
421
437
|
}
|
422
438
|
}
|
@@ -430,32 +446,39 @@ module Searchkick
|
|
430
446
|
word = options[:word] != false && (!options[:match] || options[:match] == :word)
|
431
447
|
|
432
448
|
mapping_options.values.flatten.uniq.each do |field|
|
433
|
-
|
434
|
-
type: "multi_field",
|
435
|
-
fields: {}
|
436
|
-
}
|
449
|
+
fields = {}
|
437
450
|
|
438
|
-
|
439
|
-
|
451
|
+
if mapping_options[:only_analyzed].include?(field)
|
452
|
+
fields[field] = {type: default_type, index: "no"}
|
453
|
+
else
|
454
|
+
fields[field] = keyword_mapping
|
440
455
|
end
|
441
456
|
|
442
457
|
if !options[:searchable] || mapping_options[:searchable].include?(field)
|
443
458
|
if word
|
444
|
-
|
459
|
+
fields["analyzed"] = {type: default_type, index: "analyzed", analyzer: default_analyzer}
|
445
460
|
|
446
461
|
if mapping_options[:highlight].include?(field)
|
447
|
-
|
462
|
+
fields["analyzed"][:term_vector] = "with_positions_offsets"
|
448
463
|
end
|
449
464
|
end
|
450
465
|
|
451
|
-
mapping_options.except(:highlight, :searchable, :only_analyzed).each do |type,
|
452
|
-
if options[:match] == type ||
|
453
|
-
|
466
|
+
mapping_options.except(:highlight, :searchable, :only_analyzed).each do |type, f|
|
467
|
+
if options[:match] == type || f.include?(field)
|
468
|
+
fields[type] = {type: default_type, index: "analyzed", analyzer: "searchkick_#{type}_index"}
|
454
469
|
end
|
455
470
|
end
|
456
471
|
end
|
457
472
|
|
458
|
-
mapping[field] =
|
473
|
+
mapping[field] =
|
474
|
+
if below50
|
475
|
+
{
|
476
|
+
type: "multi_field",
|
477
|
+
fields: fields
|
478
|
+
}
|
479
|
+
elsif fields[field]
|
480
|
+
fields[field].merge(fields: fields.except(field))
|
481
|
+
end
|
459
482
|
end
|
460
483
|
|
461
484
|
(options[:locations] || []).map(&:to_s).each do |field|
|
@@ -466,7 +489,7 @@ module Searchkick
|
|
466
489
|
|
467
490
|
(options[:unsearchable] || []).map(&:to_s).each do |field|
|
468
491
|
mapping[field] = {
|
469
|
-
type:
|
492
|
+
type: default_type,
|
470
493
|
index: "no"
|
471
494
|
}
|
472
495
|
end
|
@@ -484,21 +507,35 @@ module Searchkick
|
|
484
507
|
# http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
|
485
508
|
# however, we can include the not_analyzed field in _all
|
486
509
|
# and the _all index analyzer will take care of it
|
487
|
-
"{name}" =>
|
510
|
+
"{name}" => keyword_mapping.merge(include_in_all: !options[:searchable])
|
488
511
|
}
|
489
512
|
|
513
|
+
dynamic_fields["{name}"][:ignore_above] = 256 unless below22
|
514
|
+
|
490
515
|
unless options[:searchable]
|
491
516
|
if options[:match] && options[:match] != :word
|
492
|
-
dynamic_fields[options[:match]] = {type:
|
517
|
+
dynamic_fields[options[:match]] = {type: default_type, index: "analyzed", analyzer: "searchkick_#{options[:match]}_index"}
|
493
518
|
end
|
494
519
|
|
495
520
|
if word
|
496
|
-
dynamic_fields["analyzed"] = {type:
|
521
|
+
dynamic_fields["analyzed"] = {type: default_type, index: "analyzed"}
|
497
522
|
end
|
498
523
|
end
|
499
524
|
|
525
|
+
# http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
|
526
|
+
multi_field =
|
527
|
+
if below50
|
528
|
+
{
|
529
|
+
type: "multi_field",
|
530
|
+
fields: dynamic_fields
|
531
|
+
}
|
532
|
+
else
|
533
|
+
dynamic_fields["{name}"].merge(fields: dynamic_fields.except("{name}"))
|
534
|
+
end
|
535
|
+
|
500
536
|
mappings = {
|
501
537
|
_default_: {
|
538
|
+
_all: {type: default_type, index: "analyzed", analyzer: default_analyzer},
|
502
539
|
properties: mapping,
|
503
540
|
_routing: routing,
|
504
541
|
# https://gist.github.com/kimchy/2898285
|
@@ -507,11 +544,7 @@ module Searchkick
|
|
507
544
|
string_template: {
|
508
545
|
match: "*",
|
509
546
|
match_mapping_type: "string",
|
510
|
-
mapping:
|
511
|
-
# http://www.elasticsearch.org/guide/reference/mapping/multi-field-type/
|
512
|
-
type: "multi_field",
|
513
|
-
fields: dynamic_fields
|
514
|
-
}
|
547
|
+
mapping: multi_field
|
515
548
|
}
|
516
549
|
}
|
517
550
|
]
|
data/lib/searchkick/model.rb
CHANGED
@@ -15,7 +15,9 @@ module Searchkick
|
|
15
15
|
class_variable_set :@@searchkick_options, options.dup
|
16
16
|
class_variable_set :@@searchkick_klass, self
|
17
17
|
class_variable_set :@@searchkick_callbacks, callbacks
|
18
|
-
class_variable_set :@@searchkick_index, options[:index_name] ||
|
18
|
+
class_variable_set :@@searchkick_index, options[:index_name] ||
|
19
|
+
(options[:index_prefix].respond_to?(:call) && proc { [options[:index_prefix].call, model_name.plural, Searchkick.env].compact.join("_") }) ||
|
20
|
+
[options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
|
19
21
|
|
20
22
|
class << self
|
21
23
|
def searchkick_search(term = nil, options = {}, &block)
|
data/lib/searchkick/query.rb
CHANGED
@@ -149,28 +149,7 @@ module Searchkick
|
|
149
149
|
end
|
150
150
|
|
151
151
|
def prepare
|
152
|
-
boost_fields =
|
153
|
-
fields = options[:fields] || searchkick_options[:searchable]
|
154
|
-
fields =
|
155
|
-
if fields
|
156
|
-
if options[:autocomplete]
|
157
|
-
fields.map { |f| "#{f}.autocomplete" }
|
158
|
-
else
|
159
|
-
fields.map do |value|
|
160
|
-
k, v = value.is_a?(Hash) ? value.to_a.first : [value, options[:match] || searchkick_options[:match] || :word]
|
161
|
-
k2, boost = k.to_s.split("^", 2)
|
162
|
-
field = "#{k2}.#{v == :word ? 'analyzed' : v}"
|
163
|
-
boost_fields[field] = boost.to_f if boost
|
164
|
-
field
|
165
|
-
end
|
166
|
-
end
|
167
|
-
else
|
168
|
-
if options[:autocomplete]
|
169
|
-
(searchkick_options[:autocomplete] || []).map { |f| "#{f}.autocomplete" }
|
170
|
-
else
|
171
|
-
["_all"]
|
172
|
-
end
|
173
|
-
end
|
152
|
+
boost_fields, fields = set_fields
|
174
153
|
|
175
154
|
operator = options[:operator] || (options[:partial] ? "or" : "and")
|
176
155
|
|
@@ -187,7 +166,6 @@ module Searchkick
|
|
187
166
|
personalize_field = searchkick_options[:personalize]
|
188
167
|
|
189
168
|
all = term == "*"
|
190
|
-
facet_limits = {}
|
191
169
|
|
192
170
|
options[:json] ||= options[:body]
|
193
171
|
if options[:json]
|
@@ -256,10 +234,19 @@ module Searchkick
|
|
256
234
|
factor = boost_fields[field] || 1
|
257
235
|
shared_options = {
|
258
236
|
query: term,
|
259
|
-
operator: operator,
|
260
237
|
boost: 10 * factor
|
261
238
|
}
|
262
239
|
|
240
|
+
match_type =
|
241
|
+
if field.end_with?(".phrase")
|
242
|
+
field = field.sub(/\.phrase\z/, ".analyzed")
|
243
|
+
:match_phrase
|
244
|
+
else
|
245
|
+
:match
|
246
|
+
end
|
247
|
+
|
248
|
+
shared_options[:operator] = operator if match_type == :match || below50?
|
249
|
+
|
263
250
|
if field == "_all" || field.end_with?(".analyzed")
|
264
251
|
shared_options[:cutoff_frequency] = 0.001 unless operator == "and" || misspellings == false
|
265
252
|
qs.concat [
|
@@ -274,11 +261,11 @@ module Searchkick
|
|
274
261
|
qs << shared_options.merge(analyzer: analyzer)
|
275
262
|
end
|
276
263
|
|
277
|
-
if misspellings != false
|
264
|
+
if misspellings != false && (match_type == :match || below50?)
|
278
265
|
qs.concat qs.map { |q| q.except(:cutoff_frequency).merge(fuzziness: edit_distance, prefix_length: prefix_length, max_expansions: max_expansions, boost: factor).merge(transpositions) }
|
279
266
|
end
|
280
267
|
|
281
|
-
queries.concat(qs.map { |q| {
|
268
|
+
queries.concat(qs.map { |q| {match_type => {field => q}} })
|
282
269
|
end
|
283
270
|
|
284
271
|
payload = {
|
@@ -324,52 +311,9 @@ module Searchkick
|
|
324
311
|
custom_filters = []
|
325
312
|
multiply_filters = []
|
326
313
|
|
327
|
-
|
328
|
-
|
329
|
-
if
|
330
|
-
boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }]
|
331
|
-
elsif boost_by.is_a?(Hash)
|
332
|
-
multiply_by, boost_by = boost_by.partition { |_, v| v[:boost_mode] == "multiply" }.map { |i| Hash[i] }
|
333
|
-
end
|
334
|
-
boost_by[options[:boost]] = {factor: 1} if options[:boost]
|
335
|
-
|
336
|
-
custom_filters.concat boost_filters(boost_by, log: true)
|
337
|
-
multiply_filters.concat boost_filters(multiply_by || {})
|
338
|
-
|
339
|
-
boost_where = options[:boost_where] || {}
|
340
|
-
if options[:user_id] && personalize_field
|
341
|
-
boost_where[personalize_field] = options[:user_id]
|
342
|
-
end
|
343
|
-
if options[:personalize]
|
344
|
-
boost_where = boost_where.merge(options[:personalize])
|
345
|
-
end
|
346
|
-
boost_where.each do |field, value|
|
347
|
-
if value.is_a?(Array) && value.first.is_a?(Hash)
|
348
|
-
value.each do |value_factor|
|
349
|
-
custom_filters << custom_filter(field, value_factor[:value], value_factor[:factor])
|
350
|
-
end
|
351
|
-
elsif value.is_a?(Hash)
|
352
|
-
custom_filters << custom_filter(field, value[:value], value[:factor])
|
353
|
-
else
|
354
|
-
factor = 1000
|
355
|
-
custom_filters << custom_filter(field, value, factor)
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
boost_by_distance = options[:boost_by_distance]
|
360
|
-
if boost_by_distance
|
361
|
-
boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance)
|
362
|
-
if !boost_by_distance[:field] || !boost_by_distance[:origin]
|
363
|
-
raise ArgumentError, "boost_by_distance requires :field and :origin"
|
364
|
-
end
|
365
|
-
function_params = boost_by_distance.select { |k, _| [:origin, :scale, :offset, :decay].include?(k) }
|
366
|
-
function_params[:origin] = location_value(function_params[:origin])
|
367
|
-
custom_filters << {
|
368
|
-
boost_by_distance[:function] => {
|
369
|
-
boost_by_distance[:field] => function_params
|
370
|
-
}
|
371
|
-
}
|
372
|
-
end
|
314
|
+
set_boost_by(multiply_filters, custom_filters)
|
315
|
+
set_boost_where(custom_filters, personalize_field)
|
316
|
+
set_boost_by_distance(custom_filters) if options[:boost_by_distance]
|
373
317
|
|
374
318
|
if custom_filters.any?
|
375
319
|
payload = {
|
@@ -399,187 +343,23 @@ module Searchkick
|
|
399
343
|
payload[:explain] = options[:explain] if options[:explain]
|
400
344
|
|
401
345
|
# order
|
402
|
-
if options[:order]
|
403
|
-
order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
404
|
-
# TODO id transformation for arrays
|
405
|
-
payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? :_id : k, v] }]
|
406
|
-
end
|
346
|
+
set_order(payload) if options[:order]
|
407
347
|
|
408
348
|
# filters
|
409
349
|
filters = where_filters(options[:where])
|
410
|
-
if filters.any?
|
411
|
-
if options[:facets] || options[:aggs]
|
412
|
-
payload[:filter] = {
|
413
|
-
and: filters
|
414
|
-
}
|
415
|
-
else
|
416
|
-
# more efficient query if no facets
|
417
|
-
payload[:query] = {
|
418
|
-
filtered: {
|
419
|
-
query: payload[:query],
|
420
|
-
filter: {
|
421
|
-
and: filters
|
422
|
-
}
|
423
|
-
}
|
424
|
-
}
|
425
|
-
end
|
426
|
-
end
|
350
|
+
set_filters(payload, filters) if filters.any?
|
427
351
|
|
428
352
|
# facets
|
429
|
-
if options[:facets]
|
430
|
-
facets = options[:facets] || {}
|
431
|
-
facets = Hash[facets.map { |f| [f, {}] }] if facets.is_a?(Array) # convert to more advanced syntax
|
432
|
-
|
433
|
-
payload[:facets] = {}
|
434
|
-
facets.each do |field, facet_options|
|
435
|
-
# ask for extra facets due to
|
436
|
-
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
437
|
-
size = facet_options[:limit] ? facet_options[:limit] + 150 : 1_000
|
438
|
-
|
439
|
-
if facet_options[:ranges]
|
440
|
-
payload[:facets][field] = {
|
441
|
-
range: {
|
442
|
-
field.to_sym => facet_options[:ranges]
|
443
|
-
}
|
444
|
-
}
|
445
|
-
elsif facet_options[:stats]
|
446
|
-
payload[:facets][field] = {
|
447
|
-
terms_stats: {
|
448
|
-
key_field: field,
|
449
|
-
value_script: below14? ? "doc.score" : "_score",
|
450
|
-
size: size
|
451
|
-
}
|
452
|
-
}
|
453
|
-
else
|
454
|
-
payload[:facets][field] = {
|
455
|
-
terms: {
|
456
|
-
field: facet_options[:field] || field,
|
457
|
-
size: size
|
458
|
-
}
|
459
|
-
}
|
460
|
-
end
|
461
|
-
|
462
|
-
facet_limits[field] = facet_options[:limit] if facet_options[:limit]
|
463
|
-
|
464
|
-
# offset is not possible
|
465
|
-
# http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
|
466
|
-
|
467
|
-
facet_options.deep_merge!(where: options.fetch(:where, {}).reject { |k| k == field }) if options[:smart_facets] == true
|
468
|
-
facet_filters = where_filters(facet_options[:where])
|
469
|
-
if facet_filters.any?
|
470
|
-
payload[:facets][field][:facet_filter] = {
|
471
|
-
and: {
|
472
|
-
filters: facet_filters
|
473
|
-
}
|
474
|
-
}
|
475
|
-
end
|
476
|
-
end
|
477
|
-
end
|
353
|
+
set_facets(payload) if options[:facets]
|
478
354
|
|
479
355
|
# aggregations
|
480
|
-
if options[:aggs]
|
481
|
-
aggs = options[:aggs]
|
482
|
-
payload[:aggs] = {}
|
483
|
-
|
484
|
-
aggs = Hash[aggs.map { |f| [f, {}] }] if aggs.is_a?(Array) # convert to more advanced syntax
|
485
|
-
|
486
|
-
aggs.each do |field, agg_options|
|
487
|
-
size = agg_options[:limit] ? agg_options[:limit] : 1_000
|
488
|
-
shared_agg_options = agg_options.slice(:order)
|
489
|
-
|
490
|
-
if agg_options[:ranges]
|
491
|
-
payload[:aggs][field] = {
|
492
|
-
range: {
|
493
|
-
field: agg_options[:field] || field,
|
494
|
-
ranges: agg_options[:ranges]
|
495
|
-
}.merge(shared_agg_options)
|
496
|
-
}
|
497
|
-
elsif agg_options[:date_ranges]
|
498
|
-
payload[:aggs][field] = {
|
499
|
-
date_range: {
|
500
|
-
field: agg_options[:field] || field,
|
501
|
-
ranges: agg_options[:date_ranges]
|
502
|
-
}.merge(shared_agg_options)
|
503
|
-
}
|
504
|
-
else
|
505
|
-
payload[:aggs][field] = {
|
506
|
-
terms: {
|
507
|
-
field: agg_options[:field] || field,
|
508
|
-
size: size
|
509
|
-
}.merge(shared_agg_options)
|
510
|
-
}
|
511
|
-
end
|
512
|
-
|
513
|
-
where = {}
|
514
|
-
where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
|
515
|
-
agg_filters = where_filters(where.merge(agg_options[:where] || {}))
|
516
|
-
if agg_filters.any?
|
517
|
-
payload[:aggs][field] = {
|
518
|
-
filter: {
|
519
|
-
bool: {
|
520
|
-
must: agg_filters
|
521
|
-
}
|
522
|
-
},
|
523
|
-
aggs: {
|
524
|
-
field => payload[:aggs][field]
|
525
|
-
}
|
526
|
-
}
|
527
|
-
end
|
528
|
-
end
|
529
|
-
end
|
356
|
+
set_aggregations(payload) if options[:aggs]
|
530
357
|
|
531
358
|
# suggestions
|
532
|
-
if options[:suggest]
|
533
|
-
suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
|
534
|
-
|
535
|
-
# intersection
|
536
|
-
if options[:fields]
|
537
|
-
suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
|
538
|
-
end
|
539
|
-
|
540
|
-
if suggest_fields.any?
|
541
|
-
payload[:suggest] = {text: term}
|
542
|
-
suggest_fields.each do |field|
|
543
|
-
payload[:suggest][field] = {
|
544
|
-
phrase: {
|
545
|
-
field: "#{field}.suggest"
|
546
|
-
}
|
547
|
-
}
|
548
|
-
end
|
549
|
-
end
|
550
|
-
end
|
359
|
+
set_suggestions(payload) if options[:suggest]
|
551
360
|
|
552
361
|
# highlight
|
553
|
-
if options[:highlight]
|
554
|
-
payload[:highlight] = {
|
555
|
-
fields: Hash[fields.map { |f| [f, {}] }]
|
556
|
-
}
|
557
|
-
|
558
|
-
if options[:highlight].is_a?(Hash)
|
559
|
-
if (tag = options[:highlight][:tag])
|
560
|
-
payload[:highlight][:pre_tags] = [tag]
|
561
|
-
payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
|
562
|
-
end
|
563
|
-
|
564
|
-
if (fragment_size = options[:highlight][:fragment_size])
|
565
|
-
payload[:highlight][:fragment_size] = fragment_size
|
566
|
-
end
|
567
|
-
if (encoder = options[:highlight][:encoder])
|
568
|
-
payload[:highlight][:encoder] = encoder
|
569
|
-
end
|
570
|
-
|
571
|
-
highlight_fields = options[:highlight][:fields]
|
572
|
-
if highlight_fields
|
573
|
-
payload[:highlight][:fields] = {}
|
574
|
-
|
575
|
-
highlight_fields.each do |name, opts|
|
576
|
-
payload[:highlight][:fields]["#{name}.#{@match_suffix}"] = opts || {}
|
577
|
-
end
|
578
|
-
end
|
579
|
-
end
|
580
|
-
|
581
|
-
@highlighted_fields = payload[:highlight][:fields].keys
|
582
|
-
end
|
362
|
+
set_highlights(payload, fields) if options[:highlight]
|
583
363
|
|
584
364
|
# An empty array will cause only the _id and _type for each hit to be returned
|
585
365
|
# doc for :select - http://www.elasticsearch.org/guide/reference/api/search/fields/
|
@@ -606,13 +386,286 @@ module Searchkick
|
|
606
386
|
end
|
607
387
|
|
608
388
|
@body = payload
|
609
|
-
@facet_limits = facet_limits
|
389
|
+
@facet_limits = @facet_limits || {}
|
610
390
|
@page = page
|
611
391
|
@per_page = per_page
|
612
392
|
@padding = padding
|
613
393
|
@load = load
|
614
394
|
end
|
615
395
|
|
396
|
+
def set_fields
|
397
|
+
boost_fields = {}
|
398
|
+
fields = options[:fields] || searchkick_options[:searchable]
|
399
|
+
fields =
|
400
|
+
if fields
|
401
|
+
if options[:autocomplete]
|
402
|
+
fields.map { |f| "#{f}.autocomplete" }
|
403
|
+
else
|
404
|
+
fields.map do |value|
|
405
|
+
k, v = value.is_a?(Hash) ? value.to_a.first : [value, options[:match] || searchkick_options[:match] || :word]
|
406
|
+
k2, boost = k.to_s.split("^", 2)
|
407
|
+
field = "#{k2}.#{v == :word ? 'analyzed' : v}"
|
408
|
+
boost_fields[field] = boost.to_f if boost
|
409
|
+
field
|
410
|
+
end
|
411
|
+
end
|
412
|
+
else
|
413
|
+
if options[:autocomplete]
|
414
|
+
(searchkick_options[:autocomplete] || []).map { |f| "#{f}.autocomplete" }
|
415
|
+
else
|
416
|
+
["_all"]
|
417
|
+
end
|
418
|
+
end
|
419
|
+
[boost_fields, fields]
|
420
|
+
end
|
421
|
+
|
422
|
+
def set_boost_by_distance(custom_filters)
|
423
|
+
boost_by_distance = options[:boost_by_distance] || {}
|
424
|
+
boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance)
|
425
|
+
if !boost_by_distance[:field] || !boost_by_distance[:origin]
|
426
|
+
raise ArgumentError, "boost_by_distance requires :field and :origin"
|
427
|
+
end
|
428
|
+
function_params = boost_by_distance.select { |k, _| [:origin, :scale, :offset, :decay].include?(k) }
|
429
|
+
function_params[:origin] = location_value(function_params[:origin])
|
430
|
+
custom_filters << {
|
431
|
+
boost_by_distance[:function] => {
|
432
|
+
boost_by_distance[:field] => function_params
|
433
|
+
}
|
434
|
+
}
|
435
|
+
end
|
436
|
+
|
437
|
+
def set_boost_by(multiply_filters, custom_filters)
|
438
|
+
boost_by = options[:boost_by] || {}
|
439
|
+
if boost_by.is_a?(Array)
|
440
|
+
boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }]
|
441
|
+
elsif boost_by.is_a?(Hash)
|
442
|
+
multiply_by, boost_by = boost_by.partition { |_, v| v[:boost_mode] == "multiply" }.map { |i| Hash[i] }
|
443
|
+
end
|
444
|
+
boost_by[options[:boost]] = {factor: 1} if options[:boost]
|
445
|
+
|
446
|
+
custom_filters.concat boost_filters(boost_by, log: true)
|
447
|
+
multiply_filters.concat boost_filters(multiply_by || {})
|
448
|
+
end
|
449
|
+
|
450
|
+
def set_boost_where(custom_filters, personalize_field)
|
451
|
+
boost_where = options[:boost_where] || {}
|
452
|
+
if options[:user_id] && personalize_field
|
453
|
+
boost_where[personalize_field] = options[:user_id]
|
454
|
+
end
|
455
|
+
if options[:personalize]
|
456
|
+
boost_where = boost_where.merge(options[:personalize])
|
457
|
+
end
|
458
|
+
boost_where.each do |field, value|
|
459
|
+
if value.is_a?(Array) && value.first.is_a?(Hash)
|
460
|
+
value.each do |value_factor|
|
461
|
+
custom_filters << custom_filter(field, value_factor[:value], value_factor[:factor])
|
462
|
+
end
|
463
|
+
elsif value.is_a?(Hash)
|
464
|
+
custom_filters << custom_filter(field, value[:value], value[:factor])
|
465
|
+
else
|
466
|
+
factor = 1000
|
467
|
+
custom_filters << custom_filter(field, value, factor)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
def set_suggestions(payload)
|
473
|
+
suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
|
474
|
+
|
475
|
+
# intersection
|
476
|
+
if options[:fields]
|
477
|
+
suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
|
478
|
+
end
|
479
|
+
|
480
|
+
if suggest_fields.any?
|
481
|
+
payload[:suggest] = {text: term}
|
482
|
+
suggest_fields.each do |field|
|
483
|
+
payload[:suggest][field] = {
|
484
|
+
phrase: {
|
485
|
+
field: "#{field}.suggest"
|
486
|
+
}
|
487
|
+
}
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def set_highlights(payload, fields)
|
493
|
+
payload[:highlight] = {
|
494
|
+
fields: Hash[fields.map { |f| [f, {}] }]
|
495
|
+
}
|
496
|
+
|
497
|
+
if options[:highlight].is_a?(Hash)
|
498
|
+
if (tag = options[:highlight][:tag])
|
499
|
+
payload[:highlight][:pre_tags] = [tag]
|
500
|
+
payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
|
501
|
+
end
|
502
|
+
|
503
|
+
if (fragment_size = options[:highlight][:fragment_size])
|
504
|
+
payload[:highlight][:fragment_size] = fragment_size
|
505
|
+
end
|
506
|
+
if (encoder = options[:highlight][:encoder])
|
507
|
+
payload[:highlight][:encoder] = encoder
|
508
|
+
end
|
509
|
+
|
510
|
+
highlight_fields = options[:highlight][:fields]
|
511
|
+
if highlight_fields
|
512
|
+
payload[:highlight][:fields] = {}
|
513
|
+
|
514
|
+
highlight_fields.each do |name, opts|
|
515
|
+
payload[:highlight][:fields]["#{name}.#{@match_suffix}"] = opts || {}
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
@highlighted_fields = payload[:highlight][:fields].keys
|
521
|
+
end
|
522
|
+
|
523
|
+
def set_aggregations(payload)
|
524
|
+
aggs = options[:aggs]
|
525
|
+
payload[:aggs] = {}
|
526
|
+
|
527
|
+
aggs = Hash[aggs.map { |f| [f, {}] }] if aggs.is_a?(Array) # convert to more advanced syntax
|
528
|
+
|
529
|
+
aggs.each do |field, agg_options|
|
530
|
+
size = agg_options[:limit] ? agg_options[:limit] : 1_000
|
531
|
+
shared_agg_options = agg_options.slice(:order, :min_doc_count)
|
532
|
+
|
533
|
+
if agg_options[:ranges]
|
534
|
+
payload[:aggs][field] = {
|
535
|
+
range: {
|
536
|
+
field: agg_options[:field] || field,
|
537
|
+
ranges: agg_options[:ranges]
|
538
|
+
}.merge(shared_agg_options)
|
539
|
+
}
|
540
|
+
elsif agg_options[:date_ranges]
|
541
|
+
payload[:aggs][field] = {
|
542
|
+
date_range: {
|
543
|
+
field: agg_options[:field] || field,
|
544
|
+
ranges: agg_options[:date_ranges]
|
545
|
+
}.merge(shared_agg_options)
|
546
|
+
}
|
547
|
+
else
|
548
|
+
payload[:aggs][field] = {
|
549
|
+
terms: {
|
550
|
+
field: agg_options[:field] || field,
|
551
|
+
size: size
|
552
|
+
}.merge(shared_agg_options)
|
553
|
+
}
|
554
|
+
end
|
555
|
+
|
556
|
+
where = {}
|
557
|
+
where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false
|
558
|
+
agg_filters = where_filters(where.merge(agg_options[:where] || {}))
|
559
|
+
if agg_filters.any?
|
560
|
+
payload[:aggs][field] = {
|
561
|
+
filter: {
|
562
|
+
bool: {
|
563
|
+
must: agg_filters
|
564
|
+
}
|
565
|
+
},
|
566
|
+
aggs: {
|
567
|
+
field => payload[:aggs][field]
|
568
|
+
}
|
569
|
+
}
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
def set_facets(payload)
|
575
|
+
facets = options[:facets] || {}
|
576
|
+
facets = Hash[facets.map { |f| [f, {}] }] if facets.is_a?(Array) # convert to more advanced syntax
|
577
|
+
facet_limits = {}
|
578
|
+
payload[:facets] = {}
|
579
|
+
|
580
|
+
facets.each do |field, facet_options|
|
581
|
+
# ask for extra facets due to
|
582
|
+
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
583
|
+
size = facet_options[:limit] ? facet_options[:limit] + 150 : 1_000
|
584
|
+
|
585
|
+
if facet_options[:ranges]
|
586
|
+
payload[:facets][field] = {
|
587
|
+
range: {
|
588
|
+
field.to_sym => facet_options[:ranges]
|
589
|
+
}
|
590
|
+
}
|
591
|
+
elsif facet_options[:stats]
|
592
|
+
payload[:facets][field] = {
|
593
|
+
terms_stats: {
|
594
|
+
key_field: field,
|
595
|
+
value_script: below14? ? "doc.score" : "_score",
|
596
|
+
size: size
|
597
|
+
}
|
598
|
+
}
|
599
|
+
else
|
600
|
+
payload[:facets][field] = {
|
601
|
+
terms: {
|
602
|
+
field: facet_options[:field] || field,
|
603
|
+
size: size
|
604
|
+
}
|
605
|
+
}
|
606
|
+
end
|
607
|
+
|
608
|
+
facet_limits[field] = facet_options[:limit] if facet_options[:limit]
|
609
|
+
|
610
|
+
# offset is not possible
|
611
|
+
# http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
|
612
|
+
|
613
|
+
facet_options.deep_merge!(where: options.fetch(:where, {}).reject { |k| k == field }) if options[:smart_facets] == true
|
614
|
+
facet_filters = where_filters(facet_options[:where])
|
615
|
+
if facet_filters.any?
|
616
|
+
payload[:facets][field][:facet_filter] = {
|
617
|
+
and: {
|
618
|
+
filters: facet_filters
|
619
|
+
}
|
620
|
+
}
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
@facet_limits = facet_limits
|
625
|
+
end
|
626
|
+
|
627
|
+
def set_filters(payload, filters)
|
628
|
+
if options[:facets] || options[:aggs]
|
629
|
+
if below20?
|
630
|
+
payload[:filter] = {
|
631
|
+
and: filters
|
632
|
+
}
|
633
|
+
else
|
634
|
+
payload[:post_filter] = {
|
635
|
+
bool: {
|
636
|
+
filter: filters
|
637
|
+
}
|
638
|
+
}
|
639
|
+
end
|
640
|
+
else
|
641
|
+
# more efficient query if no facets
|
642
|
+
if below20?
|
643
|
+
payload[:query] = {
|
644
|
+
filtered: {
|
645
|
+
query: payload[:query],
|
646
|
+
filter: {
|
647
|
+
and: filters
|
648
|
+
}
|
649
|
+
}
|
650
|
+
}
|
651
|
+
else
|
652
|
+
payload[:query] = {
|
653
|
+
bool: {
|
654
|
+
must: payload[:query],
|
655
|
+
filter: filters
|
656
|
+
}
|
657
|
+
}
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
# TODO id transformation for arrays
|
663
|
+
def set_order(payload)
|
664
|
+
order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
665
|
+
id_field = below50? ? :_id : :_uid
|
666
|
+
payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? id_field : k, v] }]
|
667
|
+
end
|
668
|
+
|
616
669
|
def where_filters(where)
|
617
670
|
filters = []
|
618
671
|
(where || {}).each do |field, value|
|
@@ -620,7 +673,11 @@ module Searchkick
|
|
620
673
|
|
621
674
|
if field == :or
|
622
675
|
value.each do |or_clause|
|
623
|
-
|
676
|
+
if below50?
|
677
|
+
filters << {or: or_clause.map { |or_statement| {and: where_filters(or_statement)} }}
|
678
|
+
else
|
679
|
+
filters << {bool: {should: or_clause.map { |or_statement| {bool: {filter: where_filters(or_statement)}} }}}
|
680
|
+
end
|
624
681
|
end
|
625
682
|
else
|
626
683
|
# expand ranges
|
@@ -654,7 +711,11 @@ module Searchkick
|
|
654
711
|
when :regexp # support for regexp queries without using a regexp ruby object
|
655
712
|
filters << {regexp: {field => {value: op_value}}}
|
656
713
|
when :not # not equal
|
657
|
-
|
714
|
+
if below50?
|
715
|
+
filters << {not: {filter: term_filters(field, op_value)}}
|
716
|
+
else
|
717
|
+
filters << {bool: {must_not: term_filters(field, op_value)}}
|
718
|
+
end
|
658
719
|
when :all
|
659
720
|
op_value.each do |value|
|
660
721
|
filters << term_filters(field, value)
|
@@ -694,12 +755,20 @@ module Searchkick
|
|
694
755
|
def term_filters(field, value)
|
695
756
|
if value.is_a?(Array) # in query
|
696
757
|
if value.any?(&:nil?)
|
697
|
-
|
758
|
+
if below50?
|
759
|
+
{or: [term_filters(field, nil), term_filters(field, value.compact)]}
|
760
|
+
else
|
761
|
+
{bool: {should: [term_filters(field, nil), term_filters(field, value.compact)]}}
|
762
|
+
end
|
698
763
|
else
|
699
764
|
{in: {field => value}}
|
700
765
|
end
|
701
766
|
elsif value.nil?
|
702
|
-
|
767
|
+
if below50?
|
768
|
+
{missing: {field: field, existence: true, null_value: true}}
|
769
|
+
else
|
770
|
+
{bool: {must_not: {exists: {field: field}}}}
|
771
|
+
end
|
703
772
|
elsif value.is_a?(Regexp)
|
704
773
|
{regexp: {field => {value: value.source}}}
|
705
774
|
else
|
@@ -708,12 +777,19 @@ module Searchkick
|
|
708
777
|
end
|
709
778
|
|
710
779
|
def custom_filter(field, value, factor)
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
780
|
+
if below50?
|
781
|
+
{
|
782
|
+
filter: {
|
783
|
+
and: where_filters(field => value)
|
784
|
+
},
|
785
|
+
boost_factor: factor
|
786
|
+
}
|
787
|
+
else
|
788
|
+
{
|
789
|
+
filter: where_filters(field => value),
|
790
|
+
weight: factor
|
791
|
+
}
|
792
|
+
end
|
717
793
|
end
|
718
794
|
|
719
795
|
def boost_filters(boost_by, options = {})
|
@@ -747,19 +823,19 @@ module Searchkick
|
|
747
823
|
end
|
748
824
|
|
749
825
|
def below12?
|
750
|
-
|
826
|
+
Searchkick.server_below?("1.2.0")
|
751
827
|
end
|
752
828
|
|
753
829
|
def below14?
|
754
|
-
|
830
|
+
Searchkick.server_below?("1.4.0")
|
755
831
|
end
|
756
832
|
|
757
833
|
def below20?
|
758
|
-
|
834
|
+
Searchkick.server_below?("2.0.0")
|
759
835
|
end
|
760
836
|
|
761
|
-
def
|
762
|
-
|
837
|
+
def below50?
|
838
|
+
Searchkick.server_below?("5.0.0-alpha1")
|
763
839
|
end
|
764
840
|
end
|
765
841
|
end
|