searchkick 0.8.7 → 0.9.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/CHANGELOG.md +8 -1
- data/Gemfile +1 -1
- data/README.md +68 -4
- data/Rakefile +1 -1
- data/gemfiles/nobrainer.gemfile +1 -1
- data/lib/searchkick/index.rb +33 -24
- data/lib/searchkick/logging.rb +1 -1
- data/lib/searchkick/model.rb +5 -5
- data/lib/searchkick/query.rb +58 -38
- data/lib/searchkick/reindex_job.rb +1 -1
- data/lib/searchkick/reindex_v2_job.rb +1 -1
- data/lib/searchkick/results.rb +12 -12
- data/lib/searchkick/tasks.rb +3 -3
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick.rb +5 -7
- data/searchkick.gemspec +5 -5
- data/test/facets_test.rb +8 -2
- data/test/index_test.rb +13 -5
- data/test/model_test.rb +3 -0
- data/test/reindex_v2_job_test.rb +1 -1
- data/test/routing_test.rb +14 -0
- data/test/sql_test.rb +10 -5
- data/test/suggest_test.rb +2 -2
- data/test/test_helper.rb +14 -9
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 086a7f84d9047b486d127d05cc3d20b7b6d66b04
|
4
|
+
data.tar.gz: f0896d8115143f72cf088f69f82c923c6e4f67cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f084aa390d924d64c610ea86845433bb521b8ad319e50350936f56d57f3db770e01e87c34c55d38f88b58599b03d6176a8af6e4fee8793fc5f1b8fb01cfeea6e
|
7
|
+
data.tar.gz: e26a6809754329de5cf67a473f3df2d5b8b19b1981bdb22c041421b4916a1efcf5d4f78501cd9656ba297ccdcb0e3fe1742d0d57ffee1e0bf8475104045367fb
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
## 0.9.0
|
2
|
+
|
3
|
+
- Much better performance for where queries if no facets
|
4
|
+
- Added basic support for regex
|
5
|
+
- Added support for routing
|
6
|
+
- Made `Searchkick.disable_callbacks` thread-safe
|
7
|
+
|
1
8
|
## 0.8.7
|
2
9
|
|
3
|
-
- Fixed
|
10
|
+
- Fixed Mongoid import
|
4
11
|
|
5
12
|
## 0.8.6
|
6
13
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -23,9 +23,9 @@ Plus:
|
|
23
23
|
|
24
24
|
:speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][4]=true) for new features
|
25
25
|
|
26
|
-
:tangerine: Battle-tested at [Instacart](https://www.instacart.com)
|
26
|
+
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
27
27
|
|
28
|
-
[](https://travis-ci.org/ankane/searchkick)
|
29
29
|
|
30
30
|
We highly recommend tracking queries and conversions
|
31
31
|
|
@@ -606,7 +606,7 @@ Find similar items.
|
|
606
606
|
|
607
607
|
```ruby
|
608
608
|
product = Product.first
|
609
|
-
product.similar(fields: ["name"])
|
609
|
+
product.similar(fields: ["name"], where: {size: "12 oz"})
|
610
610
|
```
|
611
611
|
|
612
612
|
### Geospatial Searches
|
@@ -647,6 +647,22 @@ Also supports [additional options](http://www.elasticsearch.org/guide/en/elastic
|
|
647
647
|
City.search "san", boost_by_distance: {field: :location, origin: [37, -122], function: :linear, scale: "30mi", decay: 0.5}
|
648
648
|
```
|
649
649
|
|
650
|
+
### Routing
|
651
|
+
|
652
|
+
Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
|
653
|
+
|
654
|
+
```ruby
|
655
|
+
class Contact < ActiveRecord::Base
|
656
|
+
searchkick routing: :user_id
|
657
|
+
end
|
658
|
+
```
|
659
|
+
|
660
|
+
Reindex and search with:
|
661
|
+
|
662
|
+
```ruby
|
663
|
+
Contact.search "John", routing: current_user.id
|
664
|
+
```
|
665
|
+
|
650
666
|
## Inheritance
|
651
667
|
|
652
668
|
Searchkick supports single table inheritance.
|
@@ -849,6 +865,25 @@ product.reindex
|
|
849
865
|
product.reindex_async
|
850
866
|
```
|
851
867
|
|
868
|
+
Reindex more than one record without recreating the index
|
869
|
+
|
870
|
+
```ruby
|
871
|
+
# do this ...
|
872
|
+
some_company.products.each { |p| p.reindex }
|
873
|
+
# or this ...
|
874
|
+
Product.searchkick_index.import(some_company.products)
|
875
|
+
# don't do the following as it will recreate the index with some_company's products only
|
876
|
+
some_company.products.reindex
|
877
|
+
```
|
878
|
+
|
879
|
+
Reindex large set of records in batches
|
880
|
+
|
881
|
+
```ruby
|
882
|
+
Product.where("id > 100000").find_in_batches do |batch|
|
883
|
+
Product.searchkick_index.import(batch)
|
884
|
+
end
|
885
|
+
```
|
886
|
+
|
852
887
|
Remove old indices
|
853
888
|
|
854
889
|
```ruby
|
@@ -955,6 +990,34 @@ Reindex all models - Rails only
|
|
955
990
|
rake searchkick:reindex:all
|
956
991
|
```
|
957
992
|
|
993
|
+
## Large Data Sets
|
994
|
+
|
995
|
+
For large data sets, check out [Keeping Elasticsearch in Sync](https://www.found.no/foundation/keeping-elasticsearch-in-sync/). Searchkick will make this easy in the future.
|
996
|
+
|
997
|
+
## Testing
|
998
|
+
|
999
|
+
This section could use some love.
|
1000
|
+
|
1001
|
+
### RSpec
|
1002
|
+
|
1003
|
+
```ruby
|
1004
|
+
describe Product do
|
1005
|
+
it "searches" do
|
1006
|
+
Product.reindex
|
1007
|
+
Product.searchkick_index.refresh # don't forget this
|
1008
|
+
# test goes here...
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
```
|
1012
|
+
|
1013
|
+
### Factory Girl
|
1014
|
+
|
1015
|
+
```ruby
|
1016
|
+
product = FactoryGirl.create(:product)
|
1017
|
+
product.reindex # don't forget this
|
1018
|
+
Product.searchkick_index.refresh # or this
|
1019
|
+
```
|
1020
|
+
|
958
1021
|
## Migrating from Tire
|
959
1022
|
|
960
1023
|
1. Change `search` methods to `tire.search` and add index name in existing search calls
|
@@ -1019,11 +1082,12 @@ Thanks to Karel Minarik for [Elasticsearch Ruby](https://github.com/elasticsearc
|
|
1019
1082
|
|
1020
1083
|
## Roadmap
|
1021
1084
|
|
1085
|
+
- More features for large data sets
|
1086
|
+
- Improve section on testing
|
1022
1087
|
- Semantic search features
|
1023
1088
|
- Search multiple fields for different terms
|
1024
1089
|
- Search across models
|
1025
1090
|
- Search nested objects
|
1026
|
-
- Add section on testing
|
1027
1091
|
- Much finer customization
|
1028
1092
|
|
1029
1093
|
## Contributing
|
data/Rakefile
CHANGED
data/gemfiles/nobrainer.gemfile
CHANGED
data/lib/searchkick/index.rb
CHANGED
@@ -34,7 +34,7 @@ module Searchkick
|
|
34
34
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
35
35
|
[]
|
36
36
|
end
|
37
|
-
actions = old_indices.map{|old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
|
37
|
+
actions = old_indices.map { |old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
|
38
38
|
client.indices.update_aliases body: {actions: actions}
|
39
39
|
end
|
40
40
|
|
@@ -50,19 +50,22 @@ module Searchkick
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def remove(record)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
id = search_id(record)
|
54
|
+
unless id.blank?
|
55
|
+
client.delete(
|
56
|
+
index: name,
|
57
|
+
type: document_type(record),
|
58
|
+
id: id
|
59
|
+
)
|
60
|
+
end
|
58
61
|
end
|
59
62
|
|
60
63
|
def import(records)
|
61
|
-
records.group_by{|r| document_type(r) }.each do |type, batch|
|
64
|
+
records.group_by { |r| document_type(r) }.each do |type, batch|
|
62
65
|
client.bulk(
|
63
66
|
index: name,
|
64
67
|
type: type,
|
65
|
-
body: batch.map{|r| {index: {_id: search_id(r), data: search_data(r)}} }
|
68
|
+
body: batch.map { |r| {index: {_id: search_id(r), data: search_data(r)}} }
|
66
69
|
)
|
67
70
|
end
|
68
71
|
end
|
@@ -71,12 +74,12 @@ module Searchkick
|
|
71
74
|
client.get(
|
72
75
|
index: name,
|
73
76
|
type: document_type(record),
|
74
|
-
id: record
|
77
|
+
id: search_id(record)
|
75
78
|
)["_source"]
|
76
79
|
end
|
77
80
|
|
78
81
|
def reindex_record(record)
|
79
|
-
if record.destroyed?
|
82
|
+
if record.destroyed? || !record.should_index?
|
80
83
|
begin
|
81
84
|
remove(record)
|
82
85
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
@@ -89,7 +92,7 @@ module Searchkick
|
|
89
92
|
|
90
93
|
def reindex_record_async(record)
|
91
94
|
if defined?(Searchkick::ReindexV2Job)
|
92
|
-
|
95
|
+
Searchkick::ReindexV2Job.perform_later(record.class.name, record.id.to_s)
|
93
96
|
else
|
94
97
|
Delayed::Job.enqueue Searchkick::ReindexJob.new(record.class.name, record.id.to_s)
|
95
98
|
end
|
@@ -97,7 +100,7 @@ module Searchkick
|
|
97
100
|
|
98
101
|
def similar_record(record, options = {})
|
99
102
|
like_text = retrieve(record).to_hash
|
100
|
-
.keep_if{|k,v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
|
103
|
+
.keep_if { |k, v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
|
101
104
|
.values.compact.join(" ")
|
102
105
|
|
103
106
|
# TODO deep merge method
|
@@ -136,7 +139,7 @@ module Searchkick
|
|
136
139
|
# remove old indices that start w/ index_name
|
137
140
|
def clean_indices
|
138
141
|
all_indices = client.indices.get_aliases
|
139
|
-
indices = all_indices.select{|k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
|
142
|
+
indices = all_indices.select { |k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
|
140
143
|
indices.each do |index|
|
141
144
|
Searchkick::Index.new(index).delete
|
142
145
|
end
|
@@ -180,7 +183,7 @@ module Searchkick
|
|
180
183
|
scope = scope.search_import if scope.respond_to?(:search_import)
|
181
184
|
if scope.respond_to?(:find_in_batches)
|
182
185
|
scope.find_in_batches batch_size: batch_size do |batch|
|
183
|
-
import batch.select
|
186
|
+
import batch.select(&:should_index?)
|
184
187
|
end
|
185
188
|
else
|
186
189
|
# https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
|
@@ -200,7 +203,7 @@ module Searchkick
|
|
200
203
|
def index_options
|
201
204
|
options = @options
|
202
205
|
|
203
|
-
if options[:mappings]
|
206
|
+
if options[:mappings] && !options[:merge_mappings]
|
204
207
|
settings = options[:settings] || {}
|
205
208
|
mappings = options[:mappings]
|
206
209
|
else
|
@@ -333,7 +336,7 @@ module Searchkick
|
|
333
336
|
if synonyms.any?
|
334
337
|
settings[:analysis][:filter][:searchkick_synonym] = {
|
335
338
|
type: "synonym",
|
336
|
-
synonyms: synonyms.select{|s| s.size > 1 }.map{|s| s.join(",") }
|
339
|
+
synonyms: synonyms.select { |s| s.size > 1 }.map { |s| s.join(",") }
|
337
340
|
}
|
338
341
|
# choosing a place for the synonym filter when stemming is not easy
|
339
342
|
# https://groups.google.com/forum/#!topic/elasticsearch/p7qcQlgHdB8
|
@@ -361,7 +364,7 @@ module Searchkick
|
|
361
364
|
|
362
365
|
if options[:special_characters] == false
|
363
366
|
settings[:analysis][:analyzer].each do |analyzer, analyzer_settings|
|
364
|
-
analyzer_settings[:filter].reject!{|f| f == "asciifolding" }
|
367
|
+
analyzer_settings[:filter].reject! { |f| f == "asciifolding" }
|
365
368
|
end
|
366
369
|
end
|
367
370
|
|
@@ -380,7 +383,7 @@ module Searchkick
|
|
380
383
|
|
381
384
|
mapping_options = Hash[
|
382
385
|
[:autocomplete, :suggest, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight]
|
383
|
-
.map{|type| [type, (options[type] || []).map(&:to_s)] }
|
386
|
+
.map { |type| [type, (options[type] || []).map(&:to_s)] }
|
384
387
|
]
|
385
388
|
|
386
389
|
mapping_options.values.flatten.uniq.each do |field|
|
@@ -420,9 +423,15 @@ module Searchkick
|
|
420
423
|
}
|
421
424
|
end
|
422
425
|
|
426
|
+
routing = {}
|
427
|
+
if options[:routing]
|
428
|
+
routing = {required: true, path: options[:routing].to_s}
|
429
|
+
end
|
430
|
+
|
423
431
|
mappings = {
|
424
432
|
_default_: {
|
425
433
|
properties: mapping,
|
434
|
+
_routing: routing,
|
426
435
|
# https://gist.github.com/kimchy/2898285
|
427
436
|
dynamic_templates: [
|
428
437
|
{
|
@@ -457,7 +466,7 @@ module Searchkick
|
|
457
466
|
# other
|
458
467
|
|
459
468
|
def tokens(text, options = {})
|
460
|
-
client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map{|t| t["token"] }
|
469
|
+
client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map { |t| t["token"] }
|
461
470
|
end
|
462
471
|
|
463
472
|
def klass_document_type(klass)
|
@@ -488,24 +497,24 @@ module Searchkick
|
|
488
497
|
|
489
498
|
# stringify fields
|
490
499
|
# remove _id since search_id is used instead
|
491
|
-
source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo}.except("_id")
|
500
|
+
source = source.inject({}) { |memo, (k, v)| memo[k.to_s] = v; memo }.except("_id")
|
492
501
|
|
493
502
|
# conversions
|
494
503
|
conversions_field = options[:conversions]
|
495
|
-
if conversions_field
|
496
|
-
source[conversions_field] = source[conversions_field].map{|k, v| {query: k, count: v} }
|
504
|
+
if conversions_field && source[conversions_field]
|
505
|
+
source[conversions_field] = source[conversions_field].map { |k, v| {query: k, count: v} }
|
497
506
|
end
|
498
507
|
|
499
508
|
# hack to prevent generator field doesn't exist error
|
500
509
|
(options[:suggest] || []).map(&:to_s).each do |field|
|
501
|
-
source[field] = nil
|
510
|
+
source[field] = nil unless source[field]
|
502
511
|
end
|
503
512
|
|
504
513
|
# locations
|
505
514
|
(options[:locations] || []).map(&:to_s).each do |field|
|
506
515
|
if source[field]
|
507
516
|
if source[field].first.is_a?(Array) # array of arrays
|
508
|
-
source[field] = source[field].map{|a| a.map(&:to_f).reverse }
|
517
|
+
source[field] = source[field].map { |a| a.map(&:to_f).reverse }
|
509
518
|
else
|
510
519
|
source[field] = source[field].map(&:to_f).reverse
|
511
520
|
end
|
data/lib/searchkick/logging.rb
CHANGED
@@ -77,7 +77,7 @@ module Searchkick
|
|
77
77
|
|
78
78
|
# no easy way to tell which host the client will use
|
79
79
|
host = Searchkick.client.transport.hosts.first
|
80
|
-
debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map{|t| CGI.escape(t) }.join(
|
80
|
+
debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?pretty -d '#{payload[:query][:body].to_json}'"
|
81
81
|
end
|
82
82
|
|
83
83
|
def request(event)
|
data/lib/searchkick/model.rb
CHANGED
@@ -11,14 +11,14 @@ module Searchkick
|
|
11
11
|
class_eval do
|
12
12
|
cattr_reader :searchkick_options, :searchkick_klass
|
13
13
|
|
14
|
-
callbacks = options.
|
14
|
+
callbacks = options.key?(:callbacks) ? options[:callbacks] : true
|
15
15
|
|
16
16
|
class_variable_set :@@searchkick_options, options.dup
|
17
17
|
class_variable_set :@@searchkick_klass, self
|
18
18
|
class_variable_set :@@searchkick_callbacks, callbacks
|
19
19
|
class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
|
20
20
|
|
21
|
-
define_singleton_method(Searchkick.search_method_name) do |term = nil, options={}, &block|
|
21
|
+
define_singleton_method(Searchkick.search_method_name) do |term = nil, options = {}, &block|
|
22
22
|
searchkick_index.search_model(self, term, options, &block)
|
23
23
|
end
|
24
24
|
extend Searchkick::Reindex # legacy for Searchjoy
|
@@ -68,10 +68,10 @@ module Searchkick
|
|
68
68
|
if callbacks
|
69
69
|
callback_name = callbacks == :async ? :reindex_async : :reindex
|
70
70
|
if respond_to?(:after_commit)
|
71
|
-
after_commit callback_name, if: proc{ self.class.search_callbacks? }
|
71
|
+
after_commit callback_name, if: proc { self.class.search_callbacks? }
|
72
72
|
else
|
73
|
-
after_save callback_name, if: proc{ self.class.search_callbacks? }
|
74
|
-
after_destroy callback_name, if: proc{ self.class.search_callbacks? }
|
73
|
+
after_save callback_name, if: proc { self.class.search_callbacks? }
|
74
|
+
after_destroy callback_name, if: proc { self.class.search_callbacks? }
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
data/lib/searchkick/query.rb
CHANGED
@@ -15,26 +15,26 @@ module Searchkick
|
|
15
15
|
@term = term
|
16
16
|
@options = options
|
17
17
|
|
18
|
-
below12 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.2")
|
19
|
-
below14 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.4")
|
18
|
+
below12 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.2.0")
|
19
|
+
below14 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.4.0")
|
20
20
|
|
21
21
|
boost_fields = {}
|
22
22
|
fields =
|
23
23
|
if options[:fields]
|
24
24
|
if options[:autocomplete]
|
25
|
-
options[:fields].map{|f| "#{f}.autocomplete" }
|
25
|
+
options[:fields].map { |f| "#{f}.autocomplete" }
|
26
26
|
else
|
27
27
|
options[:fields].map do |value|
|
28
28
|
k, v = value.is_a?(Hash) ? value.to_a.first : [value, :word]
|
29
29
|
k2, boost = k.to_s.split("^", 2)
|
30
|
-
field = "#{k2}.#{v == :word ?
|
30
|
+
field = "#{k2}.#{v == :word ? 'analyzed' : v}"
|
31
31
|
boost_fields[field] = boost.to_f if boost
|
32
32
|
field
|
33
33
|
end
|
34
34
|
end
|
35
35
|
else
|
36
36
|
if options[:autocomplete]
|
37
|
-
(searchkick_options[:autocomplete] || []).map{|f| "#{f}.autocomplete" }
|
37
|
+
(searchkick_options[:autocomplete] || []).map { |f| "#{f}.autocomplete" }
|
38
38
|
else
|
39
39
|
["_all"]
|
40
40
|
end
|
@@ -44,7 +44,7 @@ module Searchkick
|
|
44
44
|
|
45
45
|
# pagination
|
46
46
|
page = [options[:page].to_i, 1].max
|
47
|
-
per_page = (options[:limit] || options[:per_page] ||
|
47
|
+
per_page = (options[:limit] || options[:per_page] || 100_000).to_i
|
48
48
|
padding = [options[:padding].to_i, 0].max
|
49
49
|
offset = options[:offset] || (page - 1) * per_page + padding
|
50
50
|
|
@@ -98,13 +98,13 @@ module Searchkick
|
|
98
98
|
boost: factor
|
99
99
|
}
|
100
100
|
|
101
|
-
if field == "_all"
|
101
|
+
if field == "_all" || field.end_with?(".analyzed")
|
102
102
|
shared_options[:cutoff_frequency] = 0.001 unless operator == "and"
|
103
103
|
qs.concat [
|
104
104
|
shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search"),
|
105
105
|
shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search2")
|
106
106
|
]
|
107
|
-
misspellings = options.
|
107
|
+
misspellings = options.key?(:misspellings) ? options[:misspellings] : options[:mispellings] # why not?
|
108
108
|
if misspellings != false
|
109
109
|
edit_distance = (misspellings.is_a?(Hash) && (misspellings[:edit_distance] || misspellings[:distance])) || 1
|
110
110
|
qs.concat [
|
@@ -120,7 +120,7 @@ module Searchkick
|
|
120
120
|
qs << shared_options.merge(analyzer: analyzer)
|
121
121
|
end
|
122
122
|
|
123
|
-
queries.concat(qs.map{|q| {match: {field => q}} })
|
123
|
+
queries.concat(qs.map { |q| {match: {field => q}} })
|
124
124
|
end
|
125
125
|
|
126
126
|
payload = {
|
@@ -130,7 +130,7 @@ module Searchkick
|
|
130
130
|
}
|
131
131
|
end
|
132
132
|
|
133
|
-
if conversions_field
|
133
|
+
if conversions_field && options[:conversions] != false
|
134
134
|
# wrap payload in a bool query
|
135
135
|
script_score =
|
136
136
|
if below12
|
@@ -167,7 +167,7 @@ module Searchkick
|
|
167
167
|
|
168
168
|
boost_by = options[:boost_by] || {}
|
169
169
|
if boost_by.is_a?(Array)
|
170
|
-
boost_by = Hash[
|
170
|
+
boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }]
|
171
171
|
end
|
172
172
|
if options[:boost]
|
173
173
|
boost_by[options[:boost]] = {factor: 1}
|
@@ -191,14 +191,14 @@ module Searchkick
|
|
191
191
|
end
|
192
192
|
|
193
193
|
boost_where = options[:boost_where] || {}
|
194
|
-
if options[:user_id]
|
194
|
+
if options[:user_id] && personalize_field
|
195
195
|
boost_where[personalize_field] = options[:user_id]
|
196
196
|
end
|
197
197
|
if options[:personalize]
|
198
198
|
boost_where = boost_where.merge(options[:personalize])
|
199
199
|
end
|
200
200
|
boost_where.each do |field, value|
|
201
|
-
if value.is_a?(Array)
|
201
|
+
if value.is_a?(Array) && value.first.is_a?(Hash)
|
202
202
|
value.each do |value_factor|
|
203
203
|
value, factor = value_factor[:value], value_factor[:factor]
|
204
204
|
custom_filters << custom_filter(field, value, factor)
|
@@ -215,10 +215,10 @@ module Searchkick
|
|
215
215
|
boost_by_distance = options[:boost_by_distance]
|
216
216
|
if boost_by_distance
|
217
217
|
boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance)
|
218
|
-
if !boost_by_distance[:field]
|
218
|
+
if !boost_by_distance[:field] || !boost_by_distance[:origin]
|
219
219
|
raise ArgumentError, "boost_by_distance requires :field and :origin"
|
220
220
|
end
|
221
|
-
function_params = boost_by_distance.select{|k,v| [:origin, :scale, :offset, :decay].include?(k) }
|
221
|
+
function_params = boost_by_distance.select { |k, v| [:origin, :scale, :offset, :decay].include?(k) }
|
222
222
|
function_params[:origin] = function_params[:origin].reverse
|
223
223
|
custom_filters << {
|
224
224
|
boost_by_distance[:function] => {
|
@@ -248,29 +248,41 @@ module Searchkick
|
|
248
248
|
if options[:order]
|
249
249
|
order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
250
250
|
# TODO id transformation for arrays
|
251
|
-
payload[:sort] = order.is_a?(Array) ? order : Hash[
|
251
|
+
payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? :_id : k, v] }]
|
252
252
|
end
|
253
253
|
|
254
254
|
# filters
|
255
255
|
filters = where_filters(options[:where])
|
256
256
|
if filters.any?
|
257
|
-
|
258
|
-
|
259
|
-
|
257
|
+
if options[:facets]
|
258
|
+
payload[:filter] = {
|
259
|
+
and: filters
|
260
|
+
}
|
261
|
+
else
|
262
|
+
# more efficient query if no facets
|
263
|
+
payload[:query] = {
|
264
|
+
filtered: {
|
265
|
+
query: payload[:query],
|
266
|
+
filter: {
|
267
|
+
and: filters
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}
|
271
|
+
end
|
260
272
|
end
|
261
273
|
|
262
274
|
# facets
|
263
275
|
if options[:facets]
|
264
276
|
facets = options[:facets] || {}
|
265
277
|
if facets.is_a?(Array) # convert to more advanced syntax
|
266
|
-
facets = Hash[
|
278
|
+
facets = Hash[facets.map { |f| [f, {}] }]
|
267
279
|
end
|
268
280
|
|
269
281
|
payload[:facets] = {}
|
270
282
|
facets.each do |field, facet_options|
|
271
283
|
# ask for extra facets due to
|
272
284
|
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
273
|
-
size = facet_options[:limit] ? facet_options[:limit] + 150 :
|
285
|
+
size = facet_options[:limit] ? facet_options[:limit] + 150 : 100_000
|
274
286
|
|
275
287
|
if facet_options[:ranges]
|
276
288
|
payload[:facets][field] = {
|
@@ -289,7 +301,7 @@ module Searchkick
|
|
289
301
|
else
|
290
302
|
payload[:facets][field] = {
|
291
303
|
terms: {
|
292
|
-
field: field,
|
304
|
+
field: facet_options[:field] || field,
|
293
305
|
size: size
|
294
306
|
}
|
295
307
|
}
|
@@ -300,7 +312,7 @@ module Searchkick
|
|
300
312
|
# offset is not possible
|
301
313
|
# http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
|
302
314
|
|
303
|
-
facet_options.deep_merge!(where: options[:where].reject{|k| k == field}) if options[:smart_facets] == true
|
315
|
+
facet_options.deep_merge!(where: options[:where].reject { |k| k == field }) if options[:smart_facets] == true
|
304
316
|
facet_filters = where_filters(facet_options[:where])
|
305
317
|
if facet_filters.any?
|
306
318
|
payload[:facets][field][:facet_filter] = {
|
@@ -318,7 +330,7 @@ module Searchkick
|
|
318
330
|
|
319
331
|
# intersection
|
320
332
|
if options[:fields]
|
321
|
-
suggest_fields
|
333
|
+
suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
|
322
334
|
end
|
323
335
|
|
324
336
|
if suggest_fields.any?
|
@@ -336,11 +348,11 @@ module Searchkick
|
|
336
348
|
# highlight
|
337
349
|
if options[:highlight]
|
338
350
|
payload[:highlight] = {
|
339
|
-
fields: Hash[
|
351
|
+
fields: Hash[fields.map { |f| [f, {}] }]
|
340
352
|
}
|
341
353
|
|
342
354
|
if options[:highlight].is_a?(Hash)
|
343
|
-
if tag = options[:highlight][:tag]
|
355
|
+
if (tag = options[:highlight][:tag])
|
344
356
|
payload[:highlight][:pre_tags] = [tag]
|
345
357
|
payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
|
346
358
|
end
|
@@ -364,8 +376,13 @@ module Searchkick
|
|
364
376
|
payload[:fields] = []
|
365
377
|
end
|
366
378
|
|
367
|
-
if options[:type]
|
368
|
-
@type = [options[:type] || klass].flatten.map{|v| searchkick_index.klass_document_type(v) }
|
379
|
+
if options[:type] || klass != searchkick_klass
|
380
|
+
@type = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v) }
|
381
|
+
end
|
382
|
+
|
383
|
+
# routing
|
384
|
+
if options[:routing]
|
385
|
+
@routing = options[:routing]
|
369
386
|
end
|
370
387
|
end
|
371
388
|
|
@@ -395,6 +412,7 @@ module Searchkick
|
|
395
412
|
body: body
|
396
413
|
}
|
397
414
|
params.merge!(type: @type) if @type
|
415
|
+
params.merge!(routing: @routing) if @routing
|
398
416
|
params
|
399
417
|
end
|
400
418
|
|
@@ -405,10 +423,10 @@ module Searchkick
|
|
405
423
|
status_code = e.message[1..3].to_i
|
406
424
|
if status_code == 404
|
407
425
|
raise MissingIndexError, "Index missing - run #{searchkick_klass.name}.reindex"
|
408
|
-
elsif status_code == 500
|
409
|
-
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]")
|
410
|
-
e.message.include?("No query registered for [multi_match]")
|
411
|
-
e.message.include?("[match] query does not support [cutoff_frequency]]")
|
426
|
+
elsif status_code == 500 && (
|
427
|
+
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
|
428
|
+
e.message.include?("No query registered for [multi_match]") ||
|
429
|
+
e.message.include?("[match] query does not support [cutoff_frequency]]") ||
|
412
430
|
e.message.include?("No query registered for [function_score]]")
|
413
431
|
)
|
414
432
|
|
@@ -430,7 +448,7 @@ module Searchkick
|
|
430
448
|
field = field.to_s
|
431
449
|
facet = response["facets"][field]
|
432
450
|
response["facets"][field]["terms"] = facet["terms"].first(limit)
|
433
|
-
response["facets"][field]["other"] = facet["total"] - facet["terms"].sum{|term| term["count"] }
|
451
|
+
response["facets"][field]["other"] = facet["total"] - facet["terms"].sum { |term| term["count"] }
|
434
452
|
end
|
435
453
|
|
436
454
|
opts = {
|
@@ -453,7 +471,7 @@ module Searchkick
|
|
453
471
|
|
454
472
|
if field == :or
|
455
473
|
value.each do |or_clause|
|
456
|
-
filters << {or: or_clause.map{|or_statement| {and: where_filters(or_statement)} }}
|
474
|
+
filters << {or: or_clause.map { |or_statement| {and: where_filters(or_statement)} }}
|
457
475
|
end
|
458
476
|
else
|
459
477
|
# expand ranges
|
@@ -507,7 +525,7 @@ module Searchkick
|
|
507
525
|
raise "Unknown where operator"
|
508
526
|
end
|
509
527
|
# issue 132
|
510
|
-
if existing = filters.find{ |f| f[:range] && f[:range][field] }
|
528
|
+
if (existing = filters.find { |f| f[:range] && f[:range][field] })
|
511
529
|
existing[:range][field].merge!(range_query)
|
512
530
|
else
|
513
531
|
filters << {range: {field => range_query}}
|
@@ -524,13 +542,15 @@ module Searchkick
|
|
524
542
|
|
525
543
|
def term_filters(field, value)
|
526
544
|
if value.is_a?(Array) # in query
|
527
|
-
if value.any?
|
528
|
-
{or:
|
545
|
+
if value.any?(&:nil?)
|
546
|
+
{or: [term_filters(field, nil), term_filters(field, value.compact)]}
|
529
547
|
else
|
530
|
-
{
|
548
|
+
{in: {field => value}}
|
531
549
|
end
|
532
550
|
elsif value.nil?
|
533
551
|
{missing: {"field" => field, existence: true, null_value: true}}
|
552
|
+
elsif value.is_a?(Regexp)
|
553
|
+
{regexp: {field => {value: value.source}}}
|
534
554
|
else
|
535
555
|
{term: {field => value}}
|
536
556
|
end
|
@@ -10,7 +10,7 @@ module Searchkick
|
|
10
10
|
model = @klass.constantize
|
11
11
|
record = model.find(@id) rescue nil # TODO fix lazy coding
|
12
12
|
index = model.searchkick_index
|
13
|
-
if !record
|
13
|
+
if !record || !record.should_index?
|
14
14
|
# hacky
|
15
15
|
record ||= model.new
|
16
16
|
record.id = @id
|
@@ -6,7 +6,7 @@ module Searchkick
|
|
6
6
|
model = klass.constantize
|
7
7
|
record = model.find(id) rescue nil # TODO fix lazy coding
|
8
8
|
index = model.searchkick_index
|
9
|
-
if !record
|
9
|
+
if !record || !record.should_index?
|
10
10
|
# hacky
|
11
11
|
record ||= model.new
|
12
12
|
record.id = id
|
data/lib/searchkick/results.rb
CHANGED
@@ -21,10 +21,10 @@ module Searchkick
|
|
21
21
|
# results can have different types
|
22
22
|
results = {}
|
23
23
|
|
24
|
-
hits.group_by{|hit, i| hit["_type"] }.each do |type, grouped_hits|
|
24
|
+
hits.group_by { |hit, i| hit["_type"] }.each do |type, grouped_hits|
|
25
25
|
records = type.camelize.constantize
|
26
26
|
if options[:includes]
|
27
|
-
if defined?(NoBrainer::Document)
|
27
|
+
if defined?(NoBrainer::Document) && records < NoBrainer::Document
|
28
28
|
records = records.preload(options[:includes])
|
29
29
|
else
|
30
30
|
records = records.includes(options[:includes])
|
@@ -35,7 +35,7 @@ module Searchkick
|
|
35
35
|
|
36
36
|
# sort
|
37
37
|
hits.map do |hit|
|
38
|
-
results[hit["_type"]].find{|r| r.id.to_s == hit["_id"].to_s }
|
38
|
+
results[hit["_type"]].find { |r| r.id.to_s == hit["_id"].to_s }
|
39
39
|
end.compact
|
40
40
|
else
|
41
41
|
hits.map do |hit|
|
@@ -54,7 +54,7 @@ module Searchkick
|
|
54
54
|
|
55
55
|
def suggestions
|
56
56
|
if response["suggest"]
|
57
|
-
response["suggest"].values.flat_map{|v| v.first["options"] }.sort_by{|o| -o["score"] }.map{|o| o["text"] }.uniq
|
57
|
+
response["suggest"].values.flat_map { |v| v.first["options"] }.sort_by { |o| -o["score"] }.map { |o| o["text"] }.uniq
|
58
58
|
else
|
59
59
|
raise "Pass `suggest: true` to the search method for suggestions"
|
60
60
|
end
|
@@ -68,7 +68,7 @@ module Searchkick
|
|
68
68
|
each_with_hit.map do |model, hit|
|
69
69
|
details = {}
|
70
70
|
if hit["highlight"]
|
71
|
-
details[:highlight] = Hash[
|
71
|
+
details[:highlight] = Hash[hit["highlight"].map { |k, v| [(options[:json] ? k : k.sub(/\.analyzed\z/, "")).to_sym, v.first] }]
|
72
72
|
end
|
73
73
|
[model, details]
|
74
74
|
end
|
@@ -138,18 +138,18 @@ module Searchkick
|
|
138
138
|
private
|
139
139
|
|
140
140
|
def results_query(records, grouped_hits)
|
141
|
-
if records.respond_to?(:primary_key)
|
141
|
+
if records.respond_to?(:primary_key) && records.primary_key
|
142
142
|
# ActiveRecord
|
143
|
-
records.where(records.primary_key => grouped_hits.map{|hit| hit["_id"] }).to_a
|
144
|
-
elsif records.respond_to?(:all)
|
143
|
+
records.where(records.primary_key => grouped_hits.map { |hit| hit["_id"] }).to_a
|
144
|
+
elsif records.respond_to?(:all) && records.all.respond_to?(:for_ids)
|
145
145
|
# Mongoid 2
|
146
|
-
records.all.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
|
146
|
+
records.all.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
|
147
147
|
elsif records.respond_to?(:queryable)
|
148
148
|
# Mongoid 3+
|
149
|
-
records.queryable.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
|
150
|
-
elsif records.respond_to?(:unscoped)
|
149
|
+
records.queryable.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
|
150
|
+
elsif records.respond_to?(:unscoped) && records.all.respond_to?(:preload)
|
151
151
|
# Nobrainer
|
152
|
-
records.unscoped.where(:id.in => grouped_hits.map{|hit| hit["_id"] }).to_a
|
152
|
+
records.unscoped.where(:id.in => grouped_hits.map { |hit| hit["_id"] }).to_a
|
153
153
|
else
|
154
154
|
raise "Not sure how to load records"
|
155
155
|
end
|
data/lib/searchkick/tasks.rb
CHANGED
@@ -3,13 +3,13 @@ require "rake"
|
|
3
3
|
namespace :searchkick do
|
4
4
|
|
5
5
|
desc "reindex model"
|
6
|
-
task :
|
6
|
+
task reindex: :environment do
|
7
7
|
if ENV["CLASS"]
|
8
8
|
klass = ENV["CLASS"].constantize rescue nil
|
9
9
|
if klass
|
10
10
|
klass.reindex
|
11
11
|
else
|
12
|
-
abort "Could not find class: #{ENV[
|
12
|
+
abort "Could not find class: #{ENV['CLASS']}"
|
13
13
|
end
|
14
14
|
else
|
15
15
|
abort "USAGE: rake searchkick:reindex CLASS=Product"
|
@@ -20,7 +20,7 @@ namespace :searchkick do
|
|
20
20
|
|
21
21
|
namespace :reindex do
|
22
22
|
desc "reindex all models"
|
23
|
-
task :
|
23
|
+
task all: :environment do
|
24
24
|
Rails.application.eager_load!
|
25
25
|
Searchkick.models.each do |model|
|
26
26
|
puts "Reindexing #{model.name}..."
|
data/lib/searchkick/version.rb
CHANGED
data/lib/searchkick.rb
CHANGED
@@ -24,13 +24,11 @@ module Searchkick
|
|
24
24
|
class InvalidQueryError < Elasticsearch::Transport::Transport::Errors::BadRequest; end
|
25
25
|
|
26
26
|
class << self
|
27
|
-
attr_accessor :callbacks
|
28
27
|
attr_accessor :search_method_name
|
29
28
|
attr_accessor :wordnet_path
|
30
29
|
attr_accessor :timeout
|
31
30
|
attr_accessor :models
|
32
31
|
end
|
33
|
-
self.callbacks = true
|
34
32
|
self.search_method_name = :search
|
35
33
|
self.wordnet_path = "/var/lib/wn_s.pl"
|
36
34
|
self.timeout = 10
|
@@ -44,8 +42,8 @@ module Searchkick
|
|
44
42
|
)
|
45
43
|
end
|
46
44
|
|
47
|
-
|
48
|
-
|
45
|
+
class << self
|
46
|
+
attr_writer :client
|
49
47
|
end
|
50
48
|
|
51
49
|
def self.server_version
|
@@ -53,15 +51,15 @@ module Searchkick
|
|
53
51
|
end
|
54
52
|
|
55
53
|
def self.enable_callbacks
|
56
|
-
|
54
|
+
Thread.current[:searchkick_callbacks_enabled] = true
|
57
55
|
end
|
58
56
|
|
59
57
|
def self.disable_callbacks
|
60
|
-
|
58
|
+
Thread.current[:searchkick_callbacks_enabled] = false
|
61
59
|
end
|
62
60
|
|
63
61
|
def self.callbacks?
|
64
|
-
|
62
|
+
Thread.current[:searchkick_callbacks_enabled].nil? || Thread.current[:searchkick_callbacks_enabled]
|
65
63
|
end
|
66
64
|
|
67
65
|
def self.env
|
data/searchkick.gemspec
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "searchkick/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "searchkick"
|
8
8
|
spec.version = Searchkick::VERSION
|
9
9
|
spec.authors = ["Andrew Kane"]
|
10
10
|
spec.email = ["andrew@chartkick.com"]
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
11
|
+
spec.description = "Intelligent search made easy"
|
12
|
+
spec.summary = "Searchkick learns what your users are looking for. As more people search, it gets smarter and the results get better. It’s friendly for developers - and magical for your users."
|
13
13
|
spec.homepage = "https://github.com/ankane/searchkick"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
-
spec.files = `git ls-files`.split(
|
16
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
data/test/facets_test.rb
CHANGED
@@ -21,6 +21,12 @@ class TestFacets < Minitest::Test
|
|
21
21
|
assert_equal ({1 => 1}), store_facet(facets: {store_id: {where: {in_stock: true}}})
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_field
|
25
|
+
assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {}})
|
26
|
+
assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {field: "store_id"}})
|
27
|
+
assert_equal ({1 => 1, 2 => 2}), store_facet({facets: {store_id_new: {field: "store_id"}}}, "store_id_new")
|
28
|
+
end
|
29
|
+
|
24
30
|
def test_limit
|
25
31
|
facet = Product.search("Product", facets: {store_id: {limit: 1}}).facets["store_id"]
|
26
32
|
assert_equal 1, facet["terms"].size
|
@@ -78,8 +84,8 @@ class TestFacets < Minitest::Test
|
|
78
84
|
|
79
85
|
protected
|
80
86
|
|
81
|
-
def store_facet(options)
|
82
|
-
Hash[
|
87
|
+
def store_facet(options, facet_key="store_id")
|
88
|
+
Hash[Product.search("Product", options).facets[facet_key]["terms"].map { |v| [v["term"], v["count"]] }]
|
83
89
|
end
|
84
90
|
|
85
91
|
end
|
data/test/index_test.rb
CHANGED
@@ -75,24 +75,32 @@ class TestIndex < Minitest::Test
|
|
75
75
|
def test_bad_mapping
|
76
76
|
Product.searchkick_index.delete
|
77
77
|
store_names ["Product A"]
|
78
|
-
assert_raises(Searchkick::InvalidQueryError){ Product.search "test" }
|
78
|
+
assert_raises(Searchkick::InvalidQueryError) { Product.search "test" }
|
79
|
+
ensure
|
80
|
+
Product.reindex
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_remove_blank_id
|
84
|
+
store_names ["Product A"]
|
85
|
+
Product.searchkick_index.remove(OpenStruct.new)
|
86
|
+
assert_search "product", ["Product A"]
|
79
87
|
ensure
|
80
88
|
Product.reindex
|
81
89
|
end
|
82
90
|
|
83
91
|
def test_missing_index
|
84
|
-
assert_raises(Searchkick::MissingIndexError){ Product.search "test", index_name: "not_found" }
|
92
|
+
assert_raises(Searchkick::MissingIndexError) { Product.search "test", index_name: "not_found" }
|
85
93
|
end
|
86
94
|
|
87
95
|
def test_unsupported_version
|
88
|
-
raises_exception =
|
96
|
+
raises_exception = ->(s) { raise Elasticsearch::Transport::Transport::Error.new("[500] No query registered for [multi_match]") }
|
89
97
|
Searchkick.client.stub :search, raises_exception do
|
90
|
-
assert_raises(Searchkick::UnsupportedVersionError){ Product.search("test") }
|
98
|
+
assert_raises(Searchkick::UnsupportedVersionError) { Product.search("test") }
|
91
99
|
end
|
92
100
|
end
|
93
101
|
|
94
102
|
def test_invalid_query
|
95
|
-
assert_raises(Searchkick::InvalidQueryError){ Product.search(query: {}) }
|
103
|
+
assert_raises(Searchkick::InvalidQueryError) { Product.search(query: {}) }
|
96
104
|
end
|
97
105
|
|
98
106
|
if defined?(ActiveRecord)
|
data/test/model_test.rb
CHANGED
data/test/reindex_v2_job_test.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class TestRouting < Minitest::Test
|
4
|
+
|
5
|
+
def test_routing_query
|
6
|
+
query = Store.search("Dollar Tree", routing: "Dollar Tree", execute: false)
|
7
|
+
assert_equal query.params[:routing], "Dollar Tree"
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_routing_mappings
|
11
|
+
index_options = Store.searchkick_index.index_options
|
12
|
+
assert_equal index_options[:mappings][:_default_][:_routing], {required: true, path: "name"}
|
13
|
+
end
|
14
|
+
end
|
data/test/sql_test.rb
CHANGED
@@ -8,7 +8,7 @@ class TestSql < Minitest::Test
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def test_no_limit
|
11
|
-
names = 20.times.map{|i| "Product #{i}" }
|
11
|
+
names = 20.times.map { |i| "Product #{i}" }
|
12
12
|
store_names names
|
13
13
|
assert_search "product", names
|
14
14
|
end
|
@@ -57,7 +57,7 @@ class TestSql < Minitest::Test
|
|
57
57
|
{name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4, user_ids: [1, 2, 3]},
|
58
58
|
{name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3, user_ids: [1]},
|
59
59
|
{name: "Product C", store_id: 3, in_stock: false, backordered: true, created_at: now - 2, orders_count: 2, user_ids: [1, 3]},
|
60
|
-
{name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1}
|
60
|
+
{name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1}
|
61
61
|
]
|
62
62
|
assert_search "product", ["Product A", "Product B"], where: {in_stock: true}
|
63
63
|
# date
|
@@ -85,7 +85,7 @@ class TestSql < Minitest::Test
|
|
85
85
|
assert_search "product", ["Product A", "Product C"], where: {user_ids: {all: [1, 3]}}
|
86
86
|
assert_search "product", [], where: {user_ids: {all: [1, 2, 3, 4]}}
|
87
87
|
# any / nested terms
|
88
|
-
assert_search "product", ["Product B", "Product C"], where: {user_ids: {not: [2], in: [1,3]}}
|
88
|
+
assert_search "product", ["Product B", "Product C"], where: {user_ids: {not: [2], in: [1, 3]}}
|
89
89
|
# not / exists
|
90
90
|
assert_search "product", ["Product D"], where: {user_ids: nil}
|
91
91
|
assert_search "product", ["Product A", "Product B", "Product C"], where: {user_ids: {not: nil}}
|
@@ -93,6 +93,11 @@ class TestSql < Minitest::Test
|
|
93
93
|
assert_search "product", ["Product B"], where: {user_ids: {not: [3, nil]}}
|
94
94
|
end
|
95
95
|
|
96
|
+
def test_regexp
|
97
|
+
store_names ["Product A"]
|
98
|
+
assert_search "*", ["Product A"], where: {name: /Pro.+/}
|
99
|
+
end
|
100
|
+
|
96
101
|
def test_where_string
|
97
102
|
store [
|
98
103
|
{name: "Product A", color: "RED"}
|
@@ -288,7 +293,7 @@ class TestSql < Minitest::Test
|
|
288
293
|
def test_select
|
289
294
|
store [{name: "Product A", store_id: 1}]
|
290
295
|
result = Product.search("product", load: false, select: [:name, :store_id]).first
|
291
|
-
assert_equal %w[id name store_id], result.keys.reject{|k| k.start_with?("_") }.sort
|
296
|
+
assert_equal %w[id name store_id], result.keys.reject { |k| k.start_with?("_") }.sort
|
292
297
|
assert_equal ["Product A"], result.name # this is not great
|
293
298
|
end
|
294
299
|
|
@@ -312,7 +317,7 @@ class TestSql < Minitest::Test
|
|
312
317
|
end
|
313
318
|
|
314
319
|
# TODO see if Mongoid is loaded
|
315
|
-
unless defined?(Mongoid)
|
320
|
+
unless defined?(Mongoid) || defined?(NoBrainer)
|
316
321
|
def test_include
|
317
322
|
store_names ["Product A"]
|
318
323
|
assert Product.search("product", include: [:store]).first.association(:store).loaded?
|
data/test/suggest_test.rb
CHANGED
@@ -19,13 +19,13 @@ class TestSuggest < Minitest::Test
|
|
19
19
|
|
20
20
|
def test_without_option
|
21
21
|
store_names ["hi"] # needed to prevent ElasticsearchException - seed 668
|
22
|
-
assert_raises(RuntimeError){ Product.search("hi").suggestions }
|
22
|
+
assert_raises(RuntimeError) { Product.search("hi").suggestions }
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_multiple_fields
|
26
26
|
store [
|
27
27
|
{name: "Shark", color: "Sharp"},
|
28
|
-
{name: "Shark", color: "Sharp"}
|
28
|
+
{name: "Shark", color: "Sharp"}
|
29
29
|
]
|
30
30
|
assert_suggest_all "shar", ["shark", "sharp"]
|
31
31
|
end
|
data/test/test_helper.rb
CHANGED
@@ -8,9 +8,11 @@ ENV["RACK_ENV"] = "test"
|
|
8
8
|
|
9
9
|
Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
|
10
10
|
|
11
|
-
File.delete("elasticsearch.log") if File.
|
11
|
+
File.delete("elasticsearch.log") if File.exist?("elasticsearch.log")
|
12
12
|
Searchkick.client.transport.logger = Logger.new("elasticsearch.log")
|
13
13
|
|
14
|
+
puts "Running against Elasticsearch #{Searchkick.server_version}"
|
15
|
+
|
14
16
|
I18n.config.enforce_available_locales = true
|
15
17
|
|
16
18
|
ActiveJob::Base.logger = nil if defined?(ActiveJob)
|
@@ -26,7 +28,7 @@ if defined?(Mongoid)
|
|
26
28
|
module BSON
|
27
29
|
class ObjectId
|
28
30
|
def <=>(other)
|
29
|
-
|
31
|
+
data <=> other.data
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -210,12 +212,15 @@ class Product
|
|
210
212
|
end
|
211
213
|
|
212
214
|
class Store
|
213
|
-
searchkick
|
214
|
-
|
215
|
-
|
216
|
-
|
215
|
+
searchkick \
|
216
|
+
routing: :name,
|
217
|
+
merge_mappings: true,
|
218
|
+
mappings: {
|
219
|
+
store: {
|
220
|
+
properties: {
|
221
|
+
name: {type: "string", analyzer: "keyword"},
|
222
|
+
}
|
217
223
|
}
|
218
|
-
}
|
219
224
|
}
|
220
225
|
end
|
221
226
|
|
@@ -223,7 +228,7 @@ class Animal
|
|
223
228
|
searchkick \
|
224
229
|
autocomplete: [:name],
|
225
230
|
suggest: [:name],
|
226
|
-
index_name: -> { "#{
|
231
|
+
index_name: -> { "#{name.tableize}-#{Date.today.year}" }
|
227
232
|
# wordnet: true
|
228
233
|
end
|
229
234
|
|
@@ -252,7 +257,7 @@ class Minitest::Test
|
|
252
257
|
end
|
253
258
|
|
254
259
|
def store_names(names, klass = Product)
|
255
|
-
store names.map{|name| {name: name} }, klass
|
260
|
+
store names.map { |name| {name: name} }, klass
|
256
261
|
end
|
257
262
|
|
258
263
|
# no order
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: searchkick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- test/query_test.rb
|
140
140
|
- test/reindex_job_test.rb
|
141
141
|
- test/reindex_v2_job_test.rb
|
142
|
+
- test/routing_test.rb
|
142
143
|
- test/should_index_test.rb
|
143
144
|
- test/similar_test.rb
|
144
145
|
- test/sql_test.rb
|
@@ -183,9 +184,11 @@ test_files:
|
|
183
184
|
- test/query_test.rb
|
184
185
|
- test/reindex_job_test.rb
|
185
186
|
- test/reindex_v2_job_test.rb
|
187
|
+
- test/routing_test.rb
|
186
188
|
- test/should_index_test.rb
|
187
189
|
- test/similar_test.rb
|
188
190
|
- test/sql_test.rb
|
189
191
|
- test/suggest_test.rb
|
190
192
|
- test/synonyms_test.rb
|
191
193
|
- test/test_helper.rb
|
194
|
+
has_rdoc:
|